插入排序族核心:維護一個已有序的前綴區間,逐個將後續元素插入合適位置。本文涵蓋直接插入、二分插入(折半插入)、希爾排序(縮小增量排序),從原理、代碼、複雜度、適用場景全方位解析。
外層遍歷 i = 1 ~ n-1,內層將 arr[i] 向左比較位移,直到找到 arr[j] <= key,插入 j+1。
void insertSort(int arr[], int n) {
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];
--j;
}
arr[j + 1] = key;
}
}
| 屬性 | 值 |
|---|---|
| 時間複雜度 | 最好 O(n)(有序)、平均/最壞 O(n²) |
| 空間複雜度 | O(1) |
| 穩定性 | 穩定 |
| 適應性 | 強(近乎有序時極快) |
| 適用場景 | 小規模 (n<50)、近乎有序、在線排序(流式數據) |
內層線性查找插入位置 → 二分查找,比較次數從 O(n) 降為 O(log n);但元素位移仍需 O(n),總體時間複雜度仍為 O(n²),僅減少比較操作。
void binaryInsertSort(int arr[], int n) {
for (int i = 1; i < n; ++i) {
int key = arr[i];
int lo = 0, hi = i - 1;
while (lo <= hi) {
int mid = (lo + hi) / 2;
if (arr[mid] > key) hi = mid - 1;
else lo = mid + 1;
}
// lo 為插入位置
for (int j = i - 1; j >= lo; --j) arr[j + 1] = arr[j];
arr[lo] = key;
}
}
gap = n/2, n/4, ..., 1(原文採用)。gap,將數組分為 gap 組(下標模 gap 相同),組內直接插入排序。gap=1 即普通插入排序,但此時數組已基本有序,極快。| 序列 | 公式 | 平均複雜度 | 備註 |
|---|---|---|---|
| Shell 原版 | n/2^k | O(n²) | 最差 |
| Hibbard | 2^k - 1 | O(n^{3/2}) | |
| Sedgewick | 9*4^k - 9*2^k + 1 或 4^k - 3*2^k + 1 | O(n^{4/3}) | 工程常用 |
| Pratt | 2^p * 3^q | O(n log² n) | 最佳理論 |
void shellSort(int arr[], int n) {
for (int gap = n / 2; gap > 0; 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;
}
}
}
| 屬性 | 值 |
|---|---|
| 時間複雜度 | 依增量序列:O(n²) ~ O(n log² n) |
| 空間複雜度 | O(1) |
| 穩定性 | 不穩定(跨組交換破壞相對序) |
| 適用場景 | 中等規模 (n ~ 1000~10000)、嵌入式無法遞歸/棧受限環境 |
| 算法 | 比較次數 | 移動次數 | 穩定 | 最佳場景 |
|---|---|---|---|---|
| 直接插入 | O(n²) | O(n²) | ✅ | 極小/近乎有序 |
| 二分插入 | O(n log n) | O(n²) | ✅ | 比較昂貴(如大對象)、近乎有序 |
| 希爾排序 | O(n^{1.5})~ | O(n^{1.5})~ | ❌ | 中等規模、原地、無遞歸 |
n < 16)改用直接插入。插入類排序以「構建有序區」為核心,直接插入簡洁穩定,二分插入優化比較,希爾排序通過分組跳躍打破 O(n²) 瓶頸。理解增量序列對希爾排序的決定性影響,是從「會用」到「精通」的分水嶺。