二路插入排序是對直接插入排序的改進,利用一個輔助數組 `b`,從中間向兩側擴展,減少元素移動次數。本文對比兩種實現:環形數組(循環利用空間)與非環形雙端擴展(雙指針向兩端擴展)。
void Grial_circular(int a[], int n) {
int b[n];
int i = 0, j = 0; // i: 右邊界, j: 左邊界(模n)
b[0] = a[0];
for (int k = 1; k < n; ++k) {
if (a[k] >= b[i]) { // 插入右端
b[(i + 1) % n] = a[k];
i = (i + 1) % n;
} else if (a[k] <= b[j]) { // 插入左端
j = (j - 1 + n) % n;
b[j] = a[k];
} else { // 插入中間,需移動元素
if (i - j + 1 <= n - (i - j + 1)) { // 移動較短的一側
// 向右移動...
} else {
// 向左移動...
}
}
}
// 輸出:從 j 開始遍歷到 i
}
void Grial_linear(int a[], int n) {
int b[n];
int i = 0, j = n - 1; // i: 左端已用, j: 右端已用
b[0] = a[0];
for (int k = 1; k < n; ++k) {
int lenL = i + 1, lenR = n - j;
if (a[k] >= b[i]) { b[++i] = a[k]; }
else if (a[k] <= b[j]) { b[--j] = a[k]; }
else if (lenL <= lenR) { // 移動左側
for (int m = i; b[m] > a[k]; --m) b[m+1] = b[m];
b[m+1] = a[k]; ++i;
} else { // 移動右側
for (int m = j; b[m] < a[k]; ++m) b[m-1] = b[m];
b[m-1] = a[k]; --j;
}
}
// 結果在 b[j..i]
for (int k = 0; k < n; ++k) a[k] = b[j + k];
}
| 特性 | 環形數組 | 非環形雙端 |
|---|---|---|
| 空間利用 | 循環覆蓋,無浪費 | 固定雙端,中間可能空閒 |
| 邊界判斷 | 模運算 `(idx±1+n)%n` | 直接索引比較 `i`, `j` |
| 中間插入移動 | 需判斷順/逆時針較短路徑 | 比較左右已用長度 `lenL`, `lenR` |
| 代碼複雜度 | 較高(模運算易出錯) | 較低,邏輯直觀 |
| 最終拷貝 | 需從 j 繞圈到 i | 連續拷貝 `b[j..i]` |
// 測試數據
int a1[] = {10,4,2,5,88,1,23}; // 7個元素
int a2[] = {2,4,77,3,1,6,3,10,8,-1}; // 10個元素
// 兩版本輸出均為升序序列