排序算法
本文内容参考 sorting-algorithms,《算法》,《算法导论》。(强迫症,最后还是把描述性的文字删了,只贴代码)
验证算法的正确性,可以使用:912. 排序数组,148. 排序链表,164. 最大间距。
简单概述
理想的排序算法
- 稳定性:相同键不会重新排序。
- 原地排序:不使用额外空间存储排序数据。
- 最坏情况 \(O(n\log{n})\) 次比较,\(O(n)\) 次交换。
- 适应性:当数据接近排序或唯一键很少时,时间复杂度为 \(O(n)\)。
如何看待常数因子的改进方案
摘抄自《算法》的一段内容:在每一节中,我们会将书中的每个算法都看做某种应用的关键。但在整体上,我们希望学习的是为每种应用找到最合适的算法。我们并不是在推荐读者一定要实现所有提到的改进方法,而是提醒大家不要对算法初始实现的性能盖棺定论。研究一个新问题时,最好的方法是先实现一个你能想到的最简单的程序,当它成为瓶颈的时候再继续改进它。实现那些只能把运行时间缩短某个常数因子的改进措施可能并不值得。你需要用实验来检验一项改进,正如本书练习所演示的那样。
冒泡排序
- 稳定性:稳定。
- 时间复杂度:\(O(n^{2})\)。
- 空间复杂度:\(O(1)\)。
- 数据接近排序时,时间复杂度为 \(O(n)\)。
1 | private static void bubbleSort(int[] nums) { |
选择排序
- 稳定性:不稳定。
- 时间复杂度:\(O(n^{2})\)。元素的交换次数最少的排序算法。
- 空间复杂度:\(O(1)\)。
1 | private static void selectionSort(int[] nums) { |
插入排序
- 稳定性:稳定。
- 时间复杂度:\(O(n^{2})\)。元素的交换次数等于逆序数。
- 空间复杂度:\(O(1)\)。
- 数据接近排序时,时间复杂度为 \(O(n)\)。
1 | private static void insertionSort(int[] nums) { |
希尔排序
- 稳定性:不稳定。
- 时间复杂度:使用 Knuth 增量序列 \(O(n^{\frac{3}{2}})\),。Knuth 增量序列:\(1,4,13,40,\dots,\frac{3^{k}-1}{2}\),相当于首项为 \(1\),公比为 \(3\) 的数列的前缀和。
- 空间复杂度:\(O(1)\)。
- 数据接近排序时,时间复杂度为 \(O(n\log{n})\)。
1 | private static void insertionSort(int[] nums) { |
归并排序
- 稳定性:稳定的。
- 时间复杂度:\(O(n\log{n})\)。
- 空间复杂度:排序数组 \(O(n)\);自顶向下排序链表 \(O(\log{n})\)(递归的空间),自底向上排序链表 \(O(1)\)。
- 顺序访问数据,缓存友好。
实现一:自顶向下(数组)
1 | private static void mergeSort(int[] nums) { |
实现二:自底向上(数组)
1 | private static void mergeSort(int[] nums) { |
实现三:自顶向下(链表)
1 | private static ListNode mergeSort(ListNode head) { |
实现四:自底向上(链表)
下面这个是没有提前计算链表长度,没有断开链表的解法。
1 | private static ListNode mergeSort(ListNode head) { |
快速排序
- 稳定性:不稳定。
- 时间复杂度:期望 \(O(n\log{n})\)。
- 空间复杂度:期望 \(O(\log{n})\)。
- 如果使用三路快排,数据包含大量重复元素时,时间复杂度为 \(O(n)\)。
实现一:二路快排
1 | private static void quickSort(int[] nums) { |
实现二:三路快排
1 | private static final Random RANDOM = new Random(); |
堆排序
- 稳定性:不稳定。
- 时间复杂度:\(O(n\log{n})\)。
- 空间复杂度:\(O(1)\)。
- 随机访问数据,缓存不友好。
1 | public static void heapSort(int[] nums) { |
计数排序
- 稳定性:稳定。
- 时间复杂度:\(O(n+k)\)。
- 空间复杂度:\(O(n+k)\)。
1 | // 元素取值范围 [0,k) |
基数排序
- 稳定性:稳定。
- 时间复杂度:\(O(d\cdot (n+k))\)。
- 空间复杂度:\(O(n+k)\)。
1 | // 元素最多有 d 位 |
桶排序
- 稳定性:取决于桶内的排序策略。
- 时间复杂度:期望 \(O(n)\)。(输入数据均匀分布时)
- 空间复杂度:\(O(n+m)\)。
1 | private static void bucketSort(int[] nums) { |