冒泡排序
|
|
- 时间复杂度:$O(N^2)$,这里 N 是数组的长度;
- 空间复杂度:$O(1)$,使用到常数个临时变量。
选择排序
|
|
算法思想 1:贪心算法:每一次决策只看当前,当前最优,则全局最优。注意:这种思想不是任何时候都适用。
算法思想 2:减治思想:外层循环每一次都能排定一个元素,问题的规模逐渐减少,直到全部解决,即「大而化小,小而化了」。运用「减治思想」很典型的算法就是大名鼎鼎的「二分查找」。
优点:交换次数最少。
- 时间复杂度:$O(N^2)$,这里 N 是数组的长度;
- 空间复杂度:$O(1)$,使用到常数个临时变量。
插入排序
|
|
优化:「将一个数字插入一个有序的数组」这一步,可以不使用逐步交换,使用先赋值给「临时变量」,然后「适当的元素」后移,空出一个位置,最后把「临时变量」赋值给这个空位的策略(就是上面那张图的意思)。编码的时候如果不小心,可能会把数组的值修改,建议多调试;
特点:「插入排序」可以提前终止内层循环(nums[j - 1] > temp 不满足时),在数组「几乎有序」的前提下,「插入排序」的时间复杂度可以达到 $O(N)$
由于「插入排序」在「几乎有序」的数组上表现良好,特别地,在「短数组」上的表现也很好。因为「短数组」的特点是:每个元素离它最终排定的位置都不会太远。为此,在小区间内执行排序任务的时候,可以转向使用「插入排序」。
- 时间复杂度:$O(N^2)$,这里 N 是数组的长度;
- 空间复杂度:$O(1)$,使用到常数个临时变量。
归并排序
|
|
优化 1:在「小区间」里转向使用「插入排序」,Java 源码里面也有类似这种操作,「小区间」的长度是个超参数; 优化 2: 在「两个数组」本身就是有序的情况下,无需合并; 优化 3:全程使用一份临时数组进行「合并两个有序数组」的操作,避免创建临时数组和销毁的消耗,避免计算下标偏移量。 注意:实现归并排序的时候,要特别注意,不要把这个算法实现成非稳定排序,区别就在 <= 和 < ,已在代码中注明。 「归并排序」比「快速排序」好的一点是,它借助了额外空间,可以实现「稳定排序」,Java 里对于「对象数组」的排序任务,就是使用归并排序(的升级版 TimSort,在这里就不多做介绍)。
复杂度分析:
时间复杂度:$O(N\log N)$,这里 N是数组的长度; 空间复杂度:$O(N^2)$,辅助数组与输入数组规模相当。
快排
|
|
实现细节(注意事项):(针对特殊测试用例:顺序数组或者逆序数组)一定要随机化选择切分元素(pivot),否则在输入数组是有序数组或者是逆序数组的时候,快速排序会变得非常慢(等同于冒泡排序或者「选择排序」);
时间复杂度:$O(N\log N)$,这里 N 是数组的长度; 空间复杂度:$O(\log N)$,这里占用的空间主要来自递归函数的栈空间。