排序是將無序序列轉為有序的基本運算。上一篇證明了有序數據配合二分查找可將 O(n) 降為 O(log n),這正是排序的核心價值。本文聚焦非遞歸 (迭代) 排序三大經典——冒泡、插入、希爾排序,從底層邏輯到代碼細節,從基礎版到優化版,構建可直接落地的工程級認知。
外層控制無序區右邊界 outer 從 n-1 遞減至 1;內層 inner 從 0 遍歷至 outer-1,相鄰比較交換,最大元素逐輪沉底。
void bubble_sort(int arr[], int n) {
if (!arr || n <= 1) return;
for (int outer = n - 1; outer >= 1; outer--) {
for (int inner = 0; inner < outer; inner++) {
if (arr[inner] > arr[inner + 1]) {
int tmp = arr[inner];
arr[inner] = arr[inner + 1];
arr[inner + 1] = tmp;
}
}
}
}
若某輪遍歷無任何交換,說明已有序,立即終止。最好情況 (已有序) 從 O(n²) 降為 O(n)。
void bubble_sort_opt(int arr[], int n) {
if (!arr || n <= 1) return;
for (int outer = n - 1; outer >= 1; outer--) {
int swapped = 0;
for (int inner = 0; inner < outer; inner++) {
if (arr[inner] > arr[inner + 1]) {
int tmp = arr[inner];
arr[inner] = arr[inner + 1];
arr[inner + 1] = tmp;
swapped = 1;
}
}
if (!swapped) break; // 關鍵優化
}
}
| 指標 | 數值 |
|---|---|
| 時間 (平均/最壞) | O(n²) |
| 時間 (最好/優化版) | O(n) |
| 空間 | O(1) |
| 穩定性 | 穩定 |
| 適用場景 | 教學、極小數據、近乎有序數據 |
維護 [0, outer-1] 為有序區。外層 outer 從 1 到 n-1,將 arr[outer] 向左比較移位,找到插入點。
void insert_sort(int arr[], int n) {
if (!arr || n <= 1) return;
for (int outer = 1; outer < n; outer++) {
int key = arr[outer];
int inner = outer - 1;
// 向右移動大於 key 的元素
while (inner >= 0 && arr[inner] > key) {
arr[inner + 1] = arr[inner];
inner--;
}
arr[inner + 1] = key; // 插入
}
}
插入排序每輪只處理局部有序區,即使某輪無移動,後續元素仍可能破壞有序性,無法像冒泡那樣全局判斷。但對近乎有序數據極其高效 (接近 O(n))。
| 指標 | 數值 |
|---|---|
| 時間 (平均/最壞) | O(n²) |
| 時間 (最好) | O(n) |
| 空間 | O(1) |
| 穩定性 | 穩定 |
| 優勢場景 | 小數組 (n < 50)、近乎有序、在線排序 (流式輸入) |
按遞減增量序列 (如 n/2, n/4, ..., 1 或 Knuth 序列 1, 4, 13, 40...) 分組,對每組執行插入排序。大步長快速移動元素接近最終位置,最後步長 1 時數組基本有序,插入排序極快。
void shell_sort(int arr[], int n) {
if (!arr || n <= 1) return;
for (int gap = n / 2; gap >= 1; gap /= 2) {
// 對每個分組做插入排序
for (int i = gap; i < n; i++) {
int key = arr[i];
int j = i - gap;
while (j >= 0 && arr[j] > key) {
arr[j + gap] = arr[j];
j -= gap;
}
arr[j + gap] = key;
}
}
}
n/2^k):最壞 O(n²),平均 ~O(n^1.5)(3^k - 1)/2):平均 O(n^1.25)| 指標 | 數值 |
|---|---|
| 時間 (平均) | O(n^1.25) ~ O(n^1.5) |
| 時間 (最壞) | 取決於增量序列 |
| 空間 | O(1) |
| 穩定性 | 不穩定 (長距離交換) |
| 工程地位 | 中等規模 (n < 5000) 高性能首選,嵌入式常用 |
| 維度 | 冒泡排序 | 插入排序 | 希爾排序 |
|---|---|---|---|
| 核心操作 | 相鄰交換 | 移位插入 | 分組插入 + 間隔 |
| 代碼複雜度 | 最低 | 低 | 中 |
| 小數據/近乎有序 | 優化版可用 | 最優 | 優 |
| 中等數據通用性能 | 差 | 一般 | 最優 |
| 穩定性需求 | 滿足 | 滿足 | 不滿足 |
| 教學價值 | 最高 (直觀) | 高 (直覺) | 中 (理解分治) |
key 和 id,排序後檢查相同 key 的 id 相對順序。clock_gettime 或 std::chrono 測量不同規模 (100, 1000, 10000) 隨機/有序/逆序數據耗時。qsort/std::sort/std::stable_sort (Introsort 混合快排/堆排/插入,O(n log n) 最壞保證)。手寫排序僅用於教學、特殊約束 (如嵌入式無 STL) 或面試。非遞歸排序雖時間復雜度多為 O(n²),但理解其局部有序構建、元素移動最小化、間隔分治等核心思想,是掌握快速排序、歸併排序、堆排序等 O(n log n) 進階算法的基石。下一篇將深入遞歸排序殿堂——快速排序。