数据结构与算法|排序算法(上)--- 比较类排序
- [第十四章 排序算法(上)--- 比较类排序](#第十四章 排序算法(上)— 比较类排序)
-
- [14.1 排序算法的基本概念](#14.1 排序算法的基本概念)
-
- [14.1.1 什么是排序](#14.1.1 什么是排序)
- [14.1.2 排序算法的评价指标](#14.1.2 排序算法的评价指标)
- [14.1.3 排序算法的分类](#14.1.3 排序算法的分类)
- [14.2 冒泡排序(Bubble Sort)](#14.2 冒泡排序(Bubble Sort))
-
- [14.2.1 基本思想](#14.2.1 基本思想)
- [14.2.2 基础实现](#14.2.2 基础实现)
- [14.2.3 优化方案](#14.2.3 优化方案)
- [14.3 选择排序(Selection Sort)](#14.3 选择排序(Selection Sort))
-
- [14.3.1 基本思想](#14.3.1 基本思想)
- [14.3.2 代码实现](#14.3.2 代码实现)
- [14.4 插入排序(Insertion Sort)](#14.4 插入排序(Insertion Sort))
-
- [14.4.1 基本思想](#14.4.1 基本思想)
- [14.4.2 代码实现](#14.4.2 代码实现)
- [14.5 希尔排序(Shell Sort)](#14.5 希尔排序(Shell Sort))
-
- [14.5.1 基本思想](#14.5.1 基本思想)
- [14.5.2 代码实现](#14.5.2 代码实现)
- [14.6 归并排序(Merge Sort)](#14.6 归并排序(Merge Sort))
-
- [14.6.1 自顶向下归并排序](#14.6.1 自顶向下归并排序)
- [14.6.2 自底向上归并排序](#14.6.2 自底向上归并排序)
- [14.7 快速排序(Quick Sort)](#14.7 快速排序(Quick Sort))
-
- [14.7.1 单路快排](#14.7.1 单路快排)
- [14.7.2 双路快排](#14.7.2 双路快排)
- [14.7.3 三路快排](#14.7.3 三路快排)
- 总结与预告
上篇:第十三章、递归与分治
第十四章 排序算法(上)--- 比较类排序
在前面的章节中,我们从数组、链表到树、图,一路学习了各种数据结构的组织与操作方式。但有一个贯穿始终的问题我们迟迟没有系统讨论------"如何将一组数据按特定顺序排列"。
排序(Sorting)是计算机科学中最基础、研究最深入的算法问题之一。据统计,商业数据处理系统中约 25% 的 CPU 周期 花在了排序上。从数据库的 ORDER BY 到搜索引擎的排名,从操作系统的进程调度到 Excel 表格的排序按钮------排序无处不在。
本章作为排序专题的上篇,聚焦 基于比较的排序算法 ,它们有一个共同特征:通过比较元素之间的大小关系来决定元素的相对顺序。我们将从最直观的冒泡排序出发,一路深入到快速排序和归并排序,建立完整的比较类排序知识体系。
14.1 排序算法的基本概念
14.1.1 什么是排序
排序(Sorting):将一个数据元素的任意序列,重新排列成一个按关键字有序的序列。
设含有 n n n 个记录的序列为:
{ R 1 , R 2 , ... , R n } \{R_1, R_2, \dots, R_n\} {R1,R2,...,Rn}
其对应的关键字序列为 { k 1 , k 2 , ... , k n } \{k_1, k_2, \dots, k_n\} {k1,k2,...,kn}。排序就是确定一种排列 p 1 , p 2 , ... , p n p_1, p_2, \dots, p_n p1,p2,...,pn,使得:
k p 1 ≤ k p 2 ≤ ⋯ ≤ k p n (递增排序) k_{p_1} \le k_{p_2} \le \dots \le k_{p_n} \quad(递增排序) kp1≤kp2≤⋯≤kpn(递增排序)
或者:
k p 1 ≥ k p 2 ≥ ⋯ ≥ k p n (递减排序) k_{p_1} \ge k_{p_2} \ge \dots \ge k_{p_n} \quad(递减排序) kp1≥kp2≥⋯≥kpn(递减排序)
14.1.2 排序算法的评价指标
评价一个排序算法,通常从以下五个维度考量:
| 评价指标 | 含义 | 说明 |
|---|---|---|
| 时间复杂度 | 最好 / 平均 / 最坏情况下的比较和移动次数 | 最核心的指标 |
| 空间复杂度 | 除输入数据外额外占用的内存 | 原地排序(In-place) 空间为 O(1) |
| 稳定性 | 相等元素的相对顺序在排序后是否保持不变 | 见下方详解 |
| 比较次数 | 元素之间比较的总次数 | 与数据初始状态有关 |
| 移动次数 | 元素实际交换或移动的总次数 | 影响常数因子 |
稳定性详解 :假设数组中有两个相等的元素 A 和 B(A 原本在 B 前面)。如果排序后 A 仍然在 B 前面,则算法是稳定的 ;否则是不稳定的。稳定性在多关键字排序(如先按成绩排序,再按姓名排序)中至关重要。
14.1.3 排序算法的分类
| 分类维度 | 类型 | 代表算法 |
|---|---|---|
| 是否基于比较 | 比较类排序 | 冒泡、选择、插入、希尔、归并、快速、堆排序 |
| 非比较类排序 | 计数排序、桶排序、基数排序 | |
| 是否原地 | 原地排序(In-place) | 冒泡、选择、插入、希尔、快速、堆排序 |
| 非原地排序 | 归并排序 | |
| 是否稳定 | 稳定排序 | 冒泡、插入、归并、计数、桶、基数 |
| 不稳定排序 | 选择、希尔、快速、堆排序 |
本章聚焦比较类排序的前六种(堆排序放在第 9 章已介绍),非比较类排序将在第 15 章展开。
14.2 冒泡排序(Bubble Sort)
14.2.1 基本思想
冒泡排序:重复遍历数组,依次比较相邻的两个元素 ,如果它们的顺序错误就交换。每一轮遍历会将当前未排序部分的最大值"浮"到末尾------就像水中的气泡缓缓上升。
核心操作:相邻比较 + 交换。每一轮确定一个元素的最终位置。

14.2.2 基础实现
java
/**
* 冒泡排序(基础版)
* 时间复杂度:最好 O(n)、平均 O(n²)、最坏 O(n²)
* 空间复杂度:O(1) ------ 原地排序
* 稳定性:稳定(相等元素不交换)
*/
public void bubbleSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) { // 共 n-1 轮
for (int j = 0; j < n - 1 - i; j++) { // 每轮比较到未排序区末尾
if (arr[j] > arr[j + 1]) { // 相邻比较
swap(arr, j, j + 1); // 逆序则交换
}
}
}
}
private void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
14.2.3 优化方案
优化一:提前终止。如果某一轮没有发生任何交换,说明数组已有序,可以提前结束。
java
/**
* 冒泡排序(优化版:提前终止)
*/
public void bubbleSortOptimized(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
boolean swapped = false; // 本轮是否发生交换
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr, j, j + 1);
swapped = true;
}
}
if (!swapped) break; // 无交换 → 已有序
}
}
优化二:记录最后交换位置。每轮记录最后一次交换的位置,该位置之后已经有序。
java
/**
* 冒泡排序(优化版:记录最后交换位置)
*/
public void bubbleSortOptimized2(int[] arr) {
int n = arr.length;
int lastSwapPos = n - 1; // 最后一轮比较的终点
while (lastSwapPos > 0) {
int newBoundary = 0; // 新的有序边界
for (int j = 0; j < lastSwapPos; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr, j, j + 1);
newBoundary = j; // 更新最后交换位置
}
}
lastSwapPos = newBoundary; // 下一轮只比较到此处
}
}
算法分析 :冒泡排序在最好情况 (已有序)下只需 O(n);最坏情况(逆序)需 O(n²);平均 O(n²)。虽然思路简单,但因交换次数多,实际性能在 O ( n 2 ) O(n^2) O(n2) 算法中最差。
14.3 选择排序(Selection Sort)
14.3.1 基本思想
选择排序:将数组分为已排序区(前部)和未排序区(后部) 。每一轮从未排序区中选出最小值,放到已排序区的末尾。
核心操作:选择最小值 + 交换到位。每一轮确定一个元素(最小值)的最终位置。
初始: [5, 3, 8, 1, 9]
↑未排序区
第1轮: 选最小 1 → 与位置0交换 → [1, 3, 8, 5, 9]
↑已排序 ↑未排序区
第2轮: 选最小 3 → 与位置1交换 → [1, 3, 8, 5, 9]
↑已排序 ↑未排序区
第3轮: 选最小 5 → 与位置2交换 → [1, 3, 5, 8, 9]
↑已排序 ↑未排序区
第4轮: 选最小 8 → 与位置3交换 → [1, 3, 5, 8, 9]
↑已排序区
14.3.2 代码实现
java
/**
* 选择排序
* 时间复杂度:O(n²)(最好 = 平均 = 最坏)
* 空间复杂度:O(1) ------ 原地排序
* 稳定性:不稳定(跳跃式交换可能破坏顺序)
*/
public void selectionSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
int minIndex = i; // 假设当前位置是最小值
for (int j = i + 1; j < n; j++) { // 在未排序区中寻找更小值
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
if (minIndex != i) { // 将最小值交换到已排序区末尾
swap(arr, i, minIndex);
}
}
}
算法分析 :无论数据初始状态如何,选择排序的比较次数固定为 n ( n − 1 ) 2 \frac{n(n-1)}{2} 2n(n−1),所以最好 = 平均 = 最坏 = O(n²) 。优点是交换次数少(最多 n−1 次),在某些写操作昂贵的场景(如 Flash 存储)中有优势。不稳定示例 :
[5, 8, 5, 2]排序后两个 5 的相对顺序发生改变。
14.4 插入排序(Insertion Sort)
14.4.1 基本思想
插入排序:将数组分为已排序区(前部)和未排序区(后部) 。每次从未排序区取出第一个元素,在已排序区中从后往前扫描 ,找到合适位置插入。
类比:打扑克牌时,你摸到一张新牌,从右往左在手中已有的牌中找到合适位置插入。
初始: [5 | 3, 8, 1, 9]
已排序 ↑未排序区
第1轮: 取 3 → 插入到 5 前面 → [3, 5 | 8, 1, 9]
第2轮: 取 8 → 插入到 5 后面 → [3, 5, 8 | 1, 9]
第3轮: 取 1 → 插入到最前面 → [1, 3, 5, 8 | 9]
第4轮: 取 9 → 插入到末尾 → [1, 3, 5, 8, 9]
14.4.2 代码实现
java
/**
* 插入排序
* 时间复杂度:最好 O(n)、平均 O(n²)、最坏 O(n²)
* 空间复杂度:O(1) ------ 原地排序
* 稳定性:稳定(相等元素不会跨越插入)
*/
public void insertionSort(int[] arr) {
int n = arr.length;
for (int i = 1; i < n; i++) { // 从第二个元素开始
int key = arr[i]; // 待插入的元素
int j = i - 1;
// 在已排序区中从后往前找插入位置
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j]; // 比 key 大的元素后移
j--;
}
arr[j + 1] = key; // 插入到正确位置
}
}
算法分析 :插入排序对近乎有序 的数据表现极佳------最好情况 O(n)。在数据量较小(n < 50)时,插入排序的常数因子极小,甚至比快速排序更快。这也是为什么快速排序在小规模子问题上会切换到插入排序。
14.5 希尔排序(Shell Sort)
14.5.1 基本思想
希尔排序:插入排序的改进版。核心思路是先让数组**"宏观有序",再逐步细化。通过引入增量(gap)**,将数组分成若干个子序列分别进行插入排序;随着增量逐渐减小至 1,最终完成一次标准的插入排序。
为什么比直接插入排序快:当 gap 较大时,元素可以"大步跳跃"到接近最终位置,减少了后续 gap=1 时的移动量。
gap=1 --- 标准插入排序
gap=3 --- 分成3组
5, _, _, 1, _, _
_, 3, _, _, 9, _
_, _, 8, _, _, 2
1, 3, 2, 5, 9, 8
14.5.2 代码实现
java
/**
* 希尔排序(使用 Knuth 增量序列:1, 4, 13, 40, 121, ...)
* 时间复杂度:取决于增量序列,Knuth 序列为 O(n^{3/2})
* 空间复杂度:O(1) ------ 原地排序
* 稳定性:不稳定(分组跳跃可能打乱相等元素的相对顺序)
*/
public void shellSort(int[] arr) {
int n = arr.length;
// 计算 Knuth 最大增量:h = 3*h + 1
int gap = 1;
while (gap < n / 3) {
gap = gap * 3 + 1; // 1, 4, 13, 40, 121, ...
}
while (gap > 0) {
// 对每个 gap 分组进行插入排序
for (int i = gap; i < n; i++) {
int key = arr[i];
int j = i;
while (j >= gap && arr[j - gap] > key) {
arr[j] = arr[j - gap];
j -= gap;
}
arr[j] = key;
}
gap /= 3; // 缩小增量
}
}
算法分析 :希尔排序的时间复杂度与增量序列密切相关 。最朴素的希尔增量( n / 2 , n / 4 , ... , 1 n/2, n/4, \dots, 1 n/2,n/4,...,1)最坏为 O ( n 2 ) O(n^2) O(n2);Hibbard 增量( 1 , 3 , 7 , 15 , ... 1, 3, 7, 15, \dots 1,3,7,15,...,即 2 k − 1 2^k - 1 2k−1)最坏为 O ( n 3 / 2 ) O(n^{3/2}) O(n3/2);Knuth 增量( 1 , 4 , 13 , 40 , ... 1, 4, 13, 40, \dots 1,4,13,40,...,即 h = 3 h + 1 h = 3h + 1 h=3h+1)同样为 O ( n 3 / 2 ) O(n^{3/2}) O(n3/2);Sedgewick 增量可达 O ( n 4 / 3 ) O(n^{4/3}) O(n4/3)。希尔排序是第一个突破 O ( n 2 ) O(n^2) O(n2) 的排序算法,在小到中等规模数据上非常实用。
14.6 归并排序(Merge Sort)
14.6.1 自顶向下归并排序
归并排序是分治法 的经典应用------将数组不断二分,直到每个子数组只有一个元素(天然有序),然后两两合并有序子数组。
在第 13 章我们已经实现过归并排序,此处换个角度------用 自底向上(迭代版) 展示另一种思路。
java
/**
* 归并排序(自顶向下 --- 递归版)
* 时间复杂度:严格 O(n log n)
* 空间复杂度:O(n)(辅助数组)
* 稳定性:稳定(合并时相等元素保持原顺序)
*/
public void mergeSort(int[] arr) {
int[] temp = new int[arr.length];
mergeSortRec(arr, 0, arr.length - 1, temp);
}
private void mergeSortRec(int[] arr, int left, int right, int[] temp) {
if (left >= right) return;
int mid = left + (right - left) / 2;
mergeSortRec(arr, left, mid, temp);
mergeSortRec(arr, mid + 1, right, temp);
merge(arr, left, mid, right, temp);
}
/** 合并两个有序子数组 [left, mid] 和 [mid+1, right] */
private void merge(int[] arr, int left, int mid, int right, int[] temp) {
int i = left, j = mid + 1, k = left;
while (i <= mid && j <= right) {
temp[k++] = (arr[i] <= arr[j]) ? arr[i++] : arr[j++];
}
while (i <= mid) temp[k++] = arr[i++];
while (j <= right) temp[k++] = arr[j++];
for (i = left; i <= right; i++) {
arr[i] = temp[i];
}
}
14.6.2 自底向上归并排序
自底向上(Bottom-Up)归并排序不使用递归,直接从单个元素开始,逐层合并:
java
/**
* 归并排序(自底向上 --- 迭代版)
* 时间复杂度:严格 O(n log n)
* 空间复杂度:O(n)
* 稳定性:稳定
*/
public void mergeSortBottomUp(int[] arr) {
int n = arr.length;
int[] temp = new int[n];
// sz:当前合并的子数组大小(1, 2, 4, 8, ...)
for (int sz = 1; sz < n; sz *= 2) {
for (int left = 0; left < n - sz; left += sz * 2) {
int mid = left + sz - 1;
int right = Math.min(left + sz * 2 - 1, n - 1);
merge(arr, left, mid, right, temp);
}
}
}
迭代版核心逻辑 :sz=1 时,将相邻的单个元素合并为长度为 2 的有序对;sz=2 时,将相邻的长度为 2 的有序对合并为长度为 4 的有序段......直到 sz >= n。
sz=4
sz=2
sz=1
5\] \[3\] → \[3,5
8\] \[1\] → \[1,8
9\] \[2\] → \[2,9
3,5\] \[1,8\] → \[1,3,5,8
2,9\] 单独留下 \[1,3,5,8\] \[2,9\] → \[1,2,3,5,8,9
算法分析 :归并排序的时间复杂度无论最好、最坏还是平均都是 O(n log n) ------这是它在基于比较的排序中最大的优势之一。缺点是空间复杂度 O(n),不是原地排序。自底向上版本避免了递归调用开销,在链表排序 (如 JDK 中
java.util.Arrays对对象数组的排序)和外部排序(磁盘文件排序)中应用广泛。
自顶向下 vs 自底向上:
| 维度 | 自顶向下(递归) | 自底向上(迭代) |
|---|---|---|
| 实现方式 | 递归 + 分治 | 迭代 + 倍增 |
| 代码可读性 | 更直观 | 稍复杂 |
| 递归栈开销 | O(log n) 栈空间 | 无递归开销 |
| 适合场景 | 理解分治思想 | 链表排序、避免 StackOverflow |
14.7 快速排序(Quick Sort)
快速排序是实际应用中最快的通用排序算法------它是 Java 中对基本类型数组 排序的默认算法(java.util.DualPivotQuicksort)。核心思想已在第 13 章介绍,本章重点讨论三种分区策略的演进。
14.7.1 单路快排
单路快排:选一个 pivot,用单指针分区------遍历数组,将小于等于 pivot 的元素放到前面,最后 pivot 归位。
java
/**
* 快速排序(单路快排)
* 时间复杂度:平均 O(n log n),最坏 O(n²)
* 空间复杂度:O(log n)(递归栈)
* 稳定性:不稳定
*/
public void quickSort(int[] arr, int low, int high) {
if (low >= high) return;
int p = partition(arr, low, high);
quickSort(arr, low, p - 1);
quickSort(arr, p + 1, high);
}
/** 单路分区:将 [low, high] 分为 <= pivot 和 > pivot 两部分 */
private int partition(int[] arr, int low, int high) {
int pivot = arr[low]; // 选第一个元素为 pivot
int j = low; // j 指向 <= pivot 区域的末尾
for (int i = low + 1; i <= high; i++) {
if (arr[i] <= pivot) {
j++;
swap(arr, i, j);
}
}
swap(arr, low, j); // pivot 归位
return j;
}
单路快排的问题 :当数组中存在大量重复元素 时,分区极不平衡(所有等于 pivot 的元素都分到一侧),导致在重复元素极多的数组上退化为 O(n²)。
14.7.2 双路快排
双路快排:使用左右双指针 ,分别从两端向中间扫描,将严格小于 pivot 的元素放左边、严格大于 pivot 的元素放右边 ,等于 pivot 的元素均匀分散到两侧。
java
/**
* 快速排序(双路快排)
* 时间复杂度:平均 O(n log n),最坏 O(n²)
* 空间复杂度:O(log n)
* 稳定性:不稳定
*/
public void quickSort2Ways(int[] arr, int low, int high) {
if (low >= high) return;
int p = partition2Ways(arr, low, high);
quickSort2Ways(arr, low, p - 1);
quickSort2Ways(arr, p + 1, high);
}
/** 双路分区 */
private int partition2Ways(int[] arr, int low, int high) {
// 随机选 pivot 并交换到首位,避免有序数组时退化
int randomIdx = low + (int)(Math.random() * (high - low + 1));
swap(arr, low, randomIdx);
int pivot = arr[low];
int left = low + 1;
int right = high;
while (true) {
while (left <= right && arr[left] < pivot) left++; // 找左边 >= pivot 的
while (left <= right && arr[right] > pivot) right--; // 找右边 <= pivot 的
if (left >= right) break;
swap(arr, left, right);
left++;
right--;
}
swap(arr, low, right); // pivot 归位
return right;
}
双路快排的优势 :等于 pivot 的元素被均匀分布到两侧,在大量重复元素场景下不会导致分区倾斜,避免了退化为 O(n²)。
14.7.3 三路快排
三路快排:将数组分为三段 ------小于 pivot、等于 pivot、大于 pivot。递归时跳过等于 pivot 的中间段。
java
/**
* 快速排序(三路快排)
* 时间复杂度:平均 O(n log n),大量重复元素时可接近 O(n)
* 空间复杂度:O(log n)
* 稳定性:不稳定
*/
public void quickSort3Ways(int[] arr, int low, int high) {
if (low >= high) return;
// 随机选 pivot 并交换到首位
int randomIdx = low + (int)(Math.random() * (high - low + 1));
swap(arr, low, randomIdx);
int pivot = arr[low];
int lt = low; // [low, lt] 小于 pivot 的区域
int gt = high + 1; // [gt, high] 大于 pivot 的区域
int i = low + 1; // 当前扫描指针
while (i < gt) {
if (arr[i] < pivot) {
lt++;
swap(arr, i, lt);
i++;
} else if (arr[i] > pivot) {
gt--;
swap(arr, i, gt);
} else {
i++; // 等于 pivot,直接跳过
}
}
swap(arr, low, lt); // pivot 归位,此时 [lt, gt-1] 全等于 pivot
quickSort3Ways(arr, low, lt - 1); // 递归处理 < pivot 的部分
quickSort3Ways(arr, gt, high); // 递归处理 > pivot 的部分
// 等于 pivot 的部分 [lt, gt-1] 已经就位,无需处理!
}
三路快排核心 :lt 指向小于区域的右边界,gt 指向大于区域的左边界。i 从左往右扫描,遇到小于 pivot 的往左扔,遇到大于 pivot 的往右扔,等于的直接走。扫描结束时 [lt+1, gt-1] 全是等于 pivot 的元素,已经排好序了。
分区结果
< pivot
=== pivot ===
> pivot
递归排序
递归排序
三种快排对比:
| 维度 | 单路快排 | 双路快排 | 三路快排 |
|---|---|---|---|
| 核心思路 | 单指针遍历 | 双指针夹逼 | 三指针划分三段 |
| 等于 pivot 的元素 | 全在一侧 | 均匀分散两侧 | 集中在中间 |
| 大量重复元素 | ❌ 退化 O(n²) | ✅ O(n log n) | ✅ 接近 O(n) |
| 有序数组 | ❌ 退化 | ✅ 随机 pivot 避免 | ✅ 随机 pivot 避免 |
| 实现复杂度 | 简单 | 中等 | 稍复杂 |
| 推荐场景 | 学习理解 | 通用场景 | 大量重复元素 |
总结与预告
本章系统学习了六种基于比较的排序算法,它们的技术路线和适用场景各不相同:
| 排序算法 | 最好时间 | 平均时间 | 最坏时间 | 空间 | 稳定性 | 核心思想 |
|---|---|---|---|---|---|---|
| 冒泡排序 | O(n) | O(n²) | O(n²) | O(1) | ✅ 稳定 | 相邻比较 + 交换,最大值"浮"到末尾 |
| 选择排序 | O(n²) | O(n²) | O(n²) | O(1) | ❌ 不稳 | 每轮选最小值放到前面 |
| 插入排序 | O(n) | O(n²) | O(n²) | O(1) | ✅ 稳定 | 从后往前找位置插入 |
| 希尔排序 | O(n log n) | O(n^{3/2}) | O(n²) | O(1) | ❌ 不稳 | gap 分组 + 插入排序 |
| 归并排序 | O(n log n) | O(n log n) | O(n log n) | O(n) | ✅ 稳定 | 分治法,先分后合 |
| 快速排序 | O(n log n) | O(n log n) | O(n²) | O(log n) | ❌ 不稳 | 分治法,先分区后递归 |
选型建议指南:
- 数据量小(n < 50) → 插入排序(常数因子最小)
- 数据近乎有序 → 插入排序或冒泡排序(优化版)
- 需要稳定排序 → 归并排序、冒泡排序、插入排序
- 内存敏感(原地排序) → 快速排序、希尔排序
- 通用场景、追求平均性能 → 快速排序(双路/三路)------它是 Java 基本类型排序的默认选择
- 链表排序 → 归并排序(自底向上,天然适配链表的 O(1) 合并)
- 外部排序(磁盘文件) → 归并排序
本章的六种算法和下一章的非比较类排序(计数排序、桶排序、基数排序)共同构成了排序算法的完整版图。下一章我们将学习那三种不依赖比较就能排序的"魔法"算法------它们能在 O(n) 时间内完成排序!
上篇:第十三章、递归与分治