常见排序算法及复杂度
冒泡排序
两层循环
外层循环:遍历数组中待排序部分
内层循环:当前元素和它后面的元素比较大小,如果逆序就交换位置;下一个元素和它后面的元素比较大小,如果逆序就交换位置...直到结束
内层循环的排序过程,就是一个冒泡的过程,很形象,当内层排序结束后,最后一个元素就是已经排好序的元素,就像气泡冒出水面了。
数组:[5,1,3,4,2]外层遍历,遍历数组待排序部分:
当前待排序部分是整个数组:
[【5,1,3,4,2】]
内层遍历,当前元素和后面的元素比较大小,如果逆序就交换位置
[(5,1),3,4,2] => [(1,5),3,4,2]
[1,(5,3),4,2] => [1,(3,5),4,2]
[1,3,(5,4),2] => [1,3,(4,5),2]
[1,3,4,(5,2)] => [1,3,4,(2,5)]
5已排好序,5之前的部分是待排序部分当前待排序部分:
[【1,3,4,2】,/5/]
内层遍历,当前元素和后面的元素比较大小,如果逆序就交换位置
[(1,3),4,2,/5/] => [(1,3),4,2,/5/]
[1,(3,4),2,/5/] => [1,(3,4),2,/5/]
[1,3,(4,2),/5/] => [1,3,(2,4),/5/]
4已排好序,4之前的部分是待排序部分...以此类推
时间复杂度:
最坏情况:
冒泡排序一共要进行(n-1)次循环,每一次循环都要进行当前n-1次比较
所以一共的比较次数是: (n-1) + (n-2) + (n-3) + … + 1 = n*(n-1)/2;
所以冒泡排序的时间复杂度是 O(n2)
最好情况:待排序数组本身就是正序的,一轮扫描即可完成排序,此时时间复杂度仅为比较时的时间复杂度,为O(n)
平均情况:O(n2)
空间复杂度:
就是在交换元素时那个临时变量所占的内存空间,最优的空间复杂度就是开始元素顺序已经排好了,则空间复杂度为0,最差的空间复杂度就是开始元素逆序排序了,则空间复杂度为O(n),平均的空间复杂度为O(1)
快速排序 快速排序有种二分法+双指针+递归的感觉
选择一个基准值,设置两个指针left和right,left负责小于基准值的部分,right负责大于基准值的部分,将数组分成小于该值、大于等于该值两部分
再将这两部分当成一个新的数组,重复上面的步骤
直到最终两部分只有1个元素为止
具体流程:
数组= [4,3,5,7,6,1,2,4]设置一个基准值,一般选择数组第一个元素即可
基准值:4
设置两个指针,left在头,right在尾基准值:4
[4(l),3,5,7,6,1,2,4(r)]
因为left指向的元素就是基准值本身,所以我们先去比较right指向的元素
right也指向4,>=基准值,属于right的范围,不做改变,right向前移动基准值:4
[4(l),3,5,7,6,1,2(r),4]
right指向2,<基准值,属于left的范围,将2赋值给left指针,left向后移动基准值:4
[2,3(l),5,7,6,1,2(r),4]
left指向3,<基准值,属于left的范围,不做改变,left向后移动基准值:4
[2,3,5(l),7,6,1,2(r),4]
left指向5,>=基准值,属于right的范围,将5赋值给right指针,right向前移动基准值:4
[2,3,5(l),7,6,1(r),5,4]
right指向1,<基准值,属于left的范围,将1赋值给left指针,left向后移动基准值:4
[2,3,1,7(l),6,1(r),5,4]
left指向7,>=基准值,属于right的范围,将7赋值给right指针,right向前移动基准值:4
[2,3,1,7(l),6(r),7,5,4]
right指向6,>=基准值,属于right的范围,不做改变,right向前移动基准值:4
[2,3,1,7(l,r),6,7,5,4]
此时left和right相遇,将该值改为基准值基准值:4
[2,3,1,4(l,r),6,7,5,4]
此时指针的左半部分[2,3,1]都是小于基准值4的
指针的右半部分[6,7,5,4]都是大于等于基准值4的
这两个部分重新选额各自的基准值,重复上述快排步骤,直到递归结束
最终就将整个数组完成排序
时间复杂度:
- 最好情况:O(nlogn)
最好情况时,每次基准值都在在数组中间并且刚好将数组平分,经过logn次划分,就分出了左右两部分
每一次划分,都涉及到n个元素
所以总共时间为O(n)*O(logn)=O(nlogn) - 最坏情况:O(n^2)
最坏情况时,每次基准值都在数组开头,并且数组完全正序,那么就需要划分n次,才能分出左右两部分
每一次划分,都涉及到n个元素
所以总共时间为O(n)*O(n)=O(n^2) - 平均:O(nlogn)
最优的情况下空间复杂度为:O(logn);每一次都平分数组的情况
最差的情况下空间复杂度为:O( n );退化为冒泡排序的情况
选择排序 将数组分为已排序部分和待排序部分
多次遍历数组的待排序部分
每次遍历,选出待排序部分的最小值,它和待排序部分的第一个元素交换位置,变成已排序部分
数组:[5,1,3,4,2]开始时,整个数组都是待排序部分
[【5,1,3,4,2】]待排序部分最小值为1,和待排序部分第一个元素5交换位置,1变成已排序
[1,【5,3,4,2】]待排序部分最小值为2,和待排序部分第一个元素5交换位置,2变成已排序
[1,2,【3,4,5】]待排序部分最小值为3,和待排序部分第一个元素3交换位置,3变成已排序
[1,2,3,【4,5】]待排序部分最小值为4,和待排序部分第一个元素4交换位置,4变成已排序
[1,2,3,4,【5】]此时待排序部分只有一个元素5,不需要排序了,就完成了全部排序
[1,2,3,4,5]
时间复杂度:O(n^2)
第一次内循环比较N - 1次,然后是N-2次,N-3次,……,最后一次内循环比较1次。
共比较的次数是 (N - 1) + (N - 2) + ... + 1,求等差数列和,得 (N - 1 + 1)* N / 2 = N^2 / 2。
舍去最高项系数,其时间复杂度为 O(N^2)。
插入排序 将数组分为已排序部分和待排序部分
开始时将数组的第一个元素当作已排序部分
遍历待排序部分的每个元素,与已排序部分的元素比较,插入到已排序部分中的正确位置上
数组:[5,1,3,4,2]开始时,第一个元素5当作已排序部分,遍历待排序部分的元素
[/5/,(1),3,4,2]待排序的1,小于已排序的5,将1放在5前面
[/1,5/,(3),4,2]待排序的3,小于已排序的5,将3放在5前面
[/1,3,5/,(4),2]
待排序的3,大于已排序的1,将3放在1后面,即不改变位置
[/1,3,5/,(4),2]待排序的4,小于已排序的5,将4放在5前面
[/1,3,4,5/,(2)]
待排序的4,大于已排序的3,将4放在3后面,即不改变位置
[/1,3,4,5/,(2)]待排序的2,小于已排序的5,将2放在5前面
[/1,3,4,2,5/]
待排序的2,小于已排序的4,将2放在4前面
[/1,3,2,4,5/]
待排序的2,小于已排序的3,将2放在3前面
[/1,2,3,4,5/]
待排序的2,大于已排序的1,将2放在1后面,即不改变位置
[/1,2,3,4,5/]排序完毕
时间复杂度:
- 最好情况:[1,2,3,4,5],遍历n-1次,移动0次,时间复杂度O(n)
- 最差情况:[5,4,3,2,1],遍历n-1次,移动1+2+...+n-1次,时间复杂度O(n^2)
- 平均:O(N^2)
然后开始向上排序合并子数组,用两个指针指向两个子数组,比较指针位置元素的大小,较小的元素先添加到数组中,并且移动该指针继续比较。
数组:[8,4,5,7,1,3,6,2]先向下递归二分数组,直到子数组不可再分
[8,4,5,7][1,3,6,2]
[8,4] [5,7][1,3] [6,2]
[8][4][5][7][1][3][6][2]开始向上递归,通过两个指针比较元素大小,排序合并每层子数组
[8][4][5][7] [1][3][6][2]
[4,8] [5,7][1,3] [2,6]
[4,5,7,8][1,2,3,6]
[1,2,3,4,5,6,7,8]指针排序过程:
例如
[4,8] [5,7]两个子序列开头设置指针
[4(i),8] [5(j),7]比较指针元素大小,较小的放入结果中,并向前移动该指针
[ ,8(i)] [5(j),7]
res=[4]重复上述步骤
[ ,8(i)] [,7(j)]
res=[4,5][ ,8(i)] [ , ]
res=[4,5,7][ ,] [ , ]
res=[4,5,7,8]
时间复杂度:O(nlogn)
一个长度为n的序列,它的排序时间就等于,它二分后的2个长度为n/2的子序列的排序时间,加上它们的合并时间。
假设长度为n的序列,排序时间为T(n),那么长度为n/2的子序列,排序时间就为T(n/2),两个n/2的子序列合并,需要操作n个元素,所以T(n)的合并时间为n
所以,T(n)=2*T(n/2)+n
【常见排序算法及复杂度】以此类推,
长度为n/2的子序列,它的排序时间T(n/2)=2*T(n/4)+n/2
长度为n/4的子序列,它的排序时间T(n/4)=2*T(n/8)+n/4
...
将T(n/4)带入到T(n/2),再将T(n/2)带入到T(n)
得到每一层T(n)
T(n)=2*T(n/2)+n
T(n)=4*T(n/4)+2n
T(n)=8*T(n/8)+3n
...
最后一层的子序列长度为1,所以最后一层的排序时间是T(1),用T(1)表示T(n)就是
logn表示:长度为n的数组,通过二分logn次,能达到不可分
T(n)=n*T(1)+(logn)*n
因为T(1)=0,所以T(n)=nlogn
推荐阅读
- 上海航芯x博联科技,一站式BMS方案(完整硬件+软件算法)
- 数据结构与算法|4 单循环链表解决约瑟夫问题
- http|常见的HTTP状态码都有哪些()
- 关于滑动时间窗口算法
- 算法|leetcode378. 有序矩阵中第 K 小的元素
- 算法|104 二叉树的最大深度(Java)
- C语言必学的数据结构|还在抱怨数据结构难? 一文带你搞懂如何AC算法题(2022版)
- CS 440 迷宫算法求解
- leetcode刷题|???算法——搜索(最短路径BFS与DFS)
- 盘点几种常见的java排序算法