(1)归并排序
1.递归法:递归非常类似于栈,后进先出,从哪里开始,就从哪里结束。归并排序的递归算法是从未知到已知,比如说,有n个元素需要排序,第一次划分成两半,就需要分别排序n/2的元素,而想要排序n/2的元素,又需要继续划分,即分成了4部分,每部分含n/4个元素,以此类推,当每部分只剩下2个元素时,可以很轻松的排序,然后递归一层一层的退出,每次进行排序的两部分都是有序的,所以这里的时间复杂度就是线性时间级的。再加上分层的部分时间复杂度为O(logn),所以整个归并排序的时间复杂度就是O(nlogn)了。详细代码见:归并排序的递归算法
2.非递归法:非递归,就是利用迭代,与递归不同的是,迭代必须从已知推向未知。排序的部分从1个元素到2个元素,一直到n/2,其实就是递归的逆过程。时间复杂度也很好理解,也是O(nlogn)。详细代码见:递归排序的非递归算法
3.区别与联系:递归是从未知推到已知,相当于把未知的东西压入栈,等到可以算出结果了,就一步一步出栈。迭代是从已知到未知,从已知的东西一步一步推至目标。递归与迭代就好像一对逆元。递归的代码更加清晰,但开销更大,也更容易出错,调试较困难;而迭代的代码编写更困难,但速度和开销较小。
4.空间占用:归并排序不是就地排序,需要一个辅助空间来存储每一阶段的排序结果。
5.属于比较排序。
(2)堆排序
堆排序是利用完全二叉树这种数据结构,但实际存储一般是存储在一维数组中,这与完全二叉树的性质有关,因为完全二叉树的结点可以与下标一一对应。关键是保持大顶堆性质的max_heapify函数,在建堆的时候调用max_heapify建立一个大顶堆。排序的时候,将最大值与数组末尾调换,并调用max_heapify保持大顶堆性质。方法非常清晰,而且没有借助辅助空间,属于就地排序,时间复杂度是O(nlogn)。详细代码见:堆排序
堆排序也属于比较排序。
(3)快速排序
快速排序的原理是,取一个基准点base(base点的研究是快排的关键),然后通过遍历,把待排序元素分成大于base和小于base的两个部分,递归进行,直到start>=end为止。由于有比较和交换的过程,且交换的跨度很大,就造成了快速排序的不稳定性,最差情况下的时间复杂度为O(n2),平均情况的下界是O(nlogn),在实际运用中,经常使用快速排序做为排序算法。详细代码见:快速排序的优化算法 快速排序的随机化版本
快速排序属于比较排序,就地排序。
(4)计数排序
计数排序有两个前提:只针对自然数且所有元素必须小于某一个值,比如k。计数排序的原理是让待排序元素成为数组的下标,再通过简单的遍历求得小于等于i的元素的个数,比如j,然后逆序遍历,保持稳定性的将i插入到第j个位置上,这样能确保前j-1个元素都是小于等于i的。计数排序需要借助其他存储空间,属于非就地排序,但其算法简单,时间复杂度为O(n),线性时间,在特定环境(元素多且小)会有很高的效率。详细代码见:计数排序及其扩展思路
(5)希尔排序
希尔排序是对插入排序的一个优化。利用增量进行多趟排序,当元素基本有序时,插入排序的时间复杂度接近O(n),希尔排序正是利用了这一点。算法非常简单,清晰。使用希尔增量的最坏时间是O(n2)。详细代码见:希尔排序(使用希尔增量)
希尔排序属于就地排序。
总结:
排序算法名称 | 时间复杂度 | 空间占用 | 使用范围 | 排序类型 |
归并排序 | O(nlogn) | 非就地排序 | 所有数据类型 | 比较排序 |
堆排序 | O(nlogn) | 就地排序 | 所有数据类型 | 比较排序 |
快速排序 | 平均下界O(nlogn) | 就地排序 | 所有数据类型 | 比较排序 |
计数排序 | O(n) | 非就地排序 | 自然数(可扩展至整数) | 非比较排序 |
希尔排序 | 希尔增量O(n2) | 就地排序 | 所有数据类型 | 比较排序 |