一、O (n²) 时间复杂度的排序算法(简单排序)
这类算法逻辑简单,适合小规模数据,但效率较低,核心是通过 "比较 - 交换" 逐步缩小无序范围。
1. 冒泡排序(Bubble Sort)
- 方法 :
从列表头部开始,重复比较相邻的两个元素,若前者大于后者则交换位置,直到最大的元素 "冒泡" 到列表末尾;然后缩小范围,对剩余元素重复操作。- 优化:若某一轮未发生交换,说明列表已有序,可提前结束。
- 复杂度 :
- 时间:最佳 O (n)(已排序),最坏 / 平均 O (n²)
- 空间:O (1)(原地排序)
- 特点:稳定排序(相等元素相对位置不变),适用于几乎有序的数据。
2. 选择排序(Selection Sort)
- 方法 :
每次从无序区中找到最小(或最大)的元素,将其与无序区的第一个元素交换,使无序区范围缩小 1,直到全部有序。 - 复杂度 :
- 时间:最佳 / 最坏 / 平均 O (n²)(无论是否有序,都需遍历找最值)
- 空间:O (1)(原地排序)
- 特点 :不稳定(例如
[3, 2, 2]
,第一个 2 可能被交换到第二个 2 后面),交换次数少(仅 n-1 次)。
3. 插入排序(Insertion Sort)
- 方法 :
将列表分为 "有序区" 和 "无序区",每次从无序区取一个元素,插入到有序区的合适位置(通过比较和后移元素),逐步扩大有序区。- 类比:整理扑克牌时,按顺序将新牌插入已有序的牌堆中。
- 复杂度 :
- 时间:最佳 O (n)(已排序),最坏 / 平均 O (n²)
- 空间:O (1)(原地排序)
- 特点:稳定排序,适用于小规模数据或部分有序数据(如接近有序的数组)。
二、O (n log n) 时间复杂度的排序算法(高效排序)
这类算法通过 "分治""归并" 等思想突破 O (n²) 瓶颈,适合大规模数据。
1. 快速排序(Quick Sort)
- 方法 :
- 选一个 "基准值"(如第一个元素),通过分区操作将列表分为两部分:左边元素均小于基准值,右边均大于基准值(基准值归位)。
- 递归对左右两部分重复上述操作,直到子列表长度为 1(天然有序)。
- 复杂度 :
- 时间:最佳 / 平均 O (n log n),最坏 O (n²)(基准值选到最值,如已排序数组)
- 优化:随机选择基准值或三数取中(避免最坏情况)。
- 空间:O (log n)~O (n)(递归栈开销,平均 log n,最坏 n)
- 时间:最佳 / 平均 O (n log n),最坏 O (n²)(基准值选到最值,如已排序数组)
- 特点:不稳定排序,实际应用中最快的排序之一(缓存友好,原地排序)。
2. 归并排序(Merge Sort)
-
方法 :
- 分:将列表递归拆分为两个子列表,直到子列表长度为 1。
- 合:将两个有序子列表 "归并" 为一个有序列表(通过双指针比较,依次取较小元素)。
- 类比:将两堆已排序的书合并为一堆,每次从两堆顶取较小的一本。
-
复杂度 :
- 时间:最佳 / 最坏 / 平均 O (n log n)(不受数据分布影响)
- 空间:O (n)(需额外数组存储归并结果)
-
特点:稳定排序,适合大规模数据或链表排序(链表归并无需额外空间)。
3. 堆排序(Heap Sort)
-
方法 :
- 将列表构建为大顶堆(父节点 >= 子节点)。
- 每次将堆顶(最大值)与堆尾元素交换,缩小堆范围,再对新堆顶执行 "下沉" 操作(维持堆结构),重复至堆为空。
- 堆:完全二叉树的一种,可通过数组索引快速访问父 / 子节点(父 i,左子 2i+1,右子 2i+2)。
-
复杂度 :
- 时间:最佳 / 最坏 / 平均 O (n log n)
- 空间:O (1)(原地排序,堆结构通过数组索引维护)
-
特点:不稳定排序,适合处理海量数据(堆结构可用于 Top K 问题)。
三、O (n) 时间复杂度的排序算法(非比较排序)
这类算法不通过元素比较实现排序,而是利用 "数值范围" 或 "哈希" 特性,仅适用于特定场景。
1. 计数排序(Counting Sort)
-
方法 :
- 找出数据的最大值和最小值,创建一个计数数组(长度为 max-min+1),统计每个值出现的次数。
- 根据计数数组的累计次数,反向填充原数组(保证稳定性)。
- 示例:排序
[2, 1, 3, 1, 2]
,计数数组统计后,按累计次数放回元素。
-
复杂度 :
- 时间:O (n + k)(n 为数据量,k 为数值范围 max-min+1)
- 空间:O (k)(计数数组开销)
-
特点:稳定排序,仅适用于数值范围小且为整数的数据(如年龄、成绩)。
2. 桶排序(Bucket Sort)
-
方法 :
- 将数据分到若干个 "桶" 中(每个桶对应一个范围),桶内数据用其他排序算法(如插入排序)排序。
- 按桶的顺序拼接所有桶的结果。
- 示例:排序
[0.42, 0.32, 0.33, 0.52, 0.37]
,按 0.1 间隔分桶,每个桶排序后拼接。
-
复杂度 :
- 时间:O (n + k)(n 为数据量,k 为桶数,若数据均匀分布,桶内排序接近 O (1))
- 空间:O (n + k)(桶的存储空间)
-
特点:稳定排序,适用于均匀分布的数据(如浮点数、身份证号分段)。
3. 基数排序(Radix Sort)
-
方法 :
- 按 "低位到高位"(或反之)依次对数据的每一位进行排序(每位用计数排序 / 桶排序)。
- 每轮排序后,数据按当前位有序,最终整体有序。
- 示例:排序
[123, 45, 678]
,先按个位排序,再按十位,最后按百位。
-
复杂度 :
- 时间:O (d*(n + k))(d 为位数,k 为每一位的基数,如十进制 k=10)
- 空间:O (n + k)(临时存储桶)
-
特点:稳定排序,适用于位数固定的数据(如整数、字符串)。
总结:排序算法对比表
排序算法 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 稳定性 | 适用场景 |
---|---|---|---|---|---|
冒泡排序 | O(n²) | O(n²) | O(1) | 稳定 | 小规模、几乎有序数据 |
选择排序 | O(n²) | O(n²) | O(1) | 不稳定 | 小规模数据,交换成本高的场景 |
插入排序 | O(n²) | O(n²) | O(1) | 稳定 | 小规模、部分有序数据 |
快速排序 | O(n log n) | O(n²) | O(log n) | 不稳定 | 大规模数据(实际应用首选) |
归并排序 | O(n log n) | O(n log n) | O(n) | 稳定 | 大规模数据、链表排序 |
堆排序 | O(n log n) | O(n log n) | O(1) | 不稳定 | 大规模数据,内存有限场景 |
计数排序 | O(n + k) | O(n + k) | O(k) | 稳定 | 数值范围小的整数 |
桶排序 | O(n + k) | O(n²) | O(n + k) | 稳定 | 均匀分布的数据 |
基数排序 | O(d*(n + k)) | O(d*(n + k)) | O(n + k) | 稳定 | 位数固定的数据(整数、字符串) |
选择建议:
小规模数据:插入排序(最快)。
- 大规模数据:快速排序(平均最优)、归并排序(稳定)、堆排序(内存紧张)。
- 特殊场景:数值范围小用计数排序,均匀分布用桶排序,位数固定用基数排序。