常见的几种排序算法
快速排序
概念:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。主要采用分治法
和挖坑填数
等方法,分治法就是大问题分解成各种小问题,对小问题求解,使得大问题得以解决。
步骤
定义一个无序数组
|
|
第一步,取基准数
基准数(枢轴),取数组的第一个元素,此时基准数:arr[0]=4
并定义两个变量i和j分别指向无序数组的第一个元素start和最后一个元素end
|
|
第二步,分区过程
分区过程,将比基准数大的数全放到它的右边,比基准数小的或者相等的数全放到它的左边。
我们首先把第一个元素arr[0]=4定义为基准元素,此时数组第一个位置就是坑,那么我们要从数组的右边向左开始查找小于基准数的元素,并与坑互换位置。
|
|
换好位置之后,现在转换,从数组的左边向右边查找比基准数大的元素:
|
|
换好位置之后,现在又重新开始从数组右边向左边开始查找,比基准数小的元素:
不断重复此类操作,直到分成左右两个分区,再把基准数填入坑中,这样第一趟排序完成。如下:
|
|
第三步,对两个区间进行分区操作
这里,我们对分好的两个区间重复进行上述分区操作,直到每个区间只有一个元素为止。
重复进行以上操作,直到左右两边分区都是有序排列,整个排序过程也就完成了:
|
|
完整代码
|
|
拓展:TOPK个数
单纯快排的话时间复杂度为$$O(nlogn)$$
基于快排的数组划分:参考LeetCode题解剑指 Offer 40. 最小的 k 个数(基于快速排序的数组划分,清晰图解) - 最小的k个数 - 力扣(LeetCode)
题目只要求返回最小的 k 个数,对这 k 个数的顺序并没有要求。因此,只需要将数组划分为 最小的 k 个数 和 其他数字 两部分即可,而快速排序的哨兵划分可完成此目标。
根据快速排序原理,如果某次哨兵划分后 基准数正好是第 k+1 小的数字 ,那么此时基准数左边的所有数字便是题目所求的 最小的 k 个数 。
根据此思路,考虑在每次哨兵划分后,判断基准数在数组中的索引是否等于 k ,若 true 则直接返回此时数组的前 k 个数字即可。
算法流程
getLeastNumbers()
函数:
1.若k大于数组长度,则直接返回整个数组
2.执行并返回quick_sort()
即可
quick_sort()
函数:
注意,此时
quick_sort()
函数的功能并不是排序整个数组,而是搜索并返回最小的k个数
- 哨兵划分:
- 划分完毕后,基准数为
arr[i]
,在/右子数组区间分别为$$[l,i-1],[i+1,r]$$
- 划分完毕后,基准数为
- 递归或返回
- 若 k*<*i,代表第 k+1 小的数字在 左子数组 中,则递归左子数组
- 若k>i ,代表第k+1 小的数字在 右子数组 中,则递归右子数组
- 若 k=i ,代表此时
arr[k]
即为第k+1 小的数字,则直接返回数组前 k 个数字即可
时间复杂度:$\Theta(\ N)$
其中 $N$ 为数组元素数量;对于长度为 $N$ 的数组执行哨兵划分操作的时间复杂度为 $\Theta(N)$ ;每轮哨兵划分后根据 $k$ 和 $i$ 的大小关系选择递归,由于 $i$ 分布的随机性,则向下递归子数组的平均长度为 $\frac{N}{2}$ ;因此平均情况下,哨兵划分操作一共有 $N+\frac{N}{2}+\frac{N}{4}+…+\frac{N}{N}=\frac{N-\frac{1}{2}}{1-\frac{1}{2}}=2N-1$(等比数列求和) 空间复杂度 $\Theta(\log N)$ : 划分函数的平均递归深度为 $\Theta(\log N)$ 。
|
|
归并排序
- 归并排序是典型的分治的应用
算法思路
归并排序两个基本操作,一个是分,也就是把原数组划分为两个子数组的过程,另一个是治,将两个有序数组合并成一个更大的有序数组。
- 将待排序的线性表不断地切分成若干个子表,直到每个子表只包含一个元素,这时,可以认为只包含一个元素的子表是有序表。
- 将子表两两合并,每合并一次,就会产生一个新的且更长的有序表,重复这一步骤,直到最后只剩下一个子表,这个子表就是排好序的线性表。
分而治之
可以看到这种结构很像一棵完全二叉树,本文的归并排序我们采用递归去实现(也可采用迭代的方式去实现)。分阶段可以理解为就是递归拆分子序列的过程,递归深度为log2n。
治阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤
动画演示
时间复杂度:$$O(nlogn)$$仅次于快排
空间复杂度:$$O(N)$$
代码实现
|
|
拓展:数组中的逆序对
代码实现:
|
|