希爾排序是插入排序的改進版,又稱縮小增量排序。核心思想:先按增量 gap 將陣列分為若干子序列,對每個子序列進行直接插入排序;逐漸縮小增量至 1,最終進行一次完整插入排序。利用「近乎有序時插入排序極快」的特性,大幅減少移動次數。
| 指標 | 複雜度 | 說明 |
|---|---|---|
| 時間 (最好) | O(n log n) | 近乎有序資料 |
| 時間 (平均) | O(n1.25) ~ O(n1.5) | 取決於增量序列 |
| 時間 (最壞) | O(n²) | 特定惡意資料 |
| 空間 | O(1) | 原地排序 |
| 穩定性 | 不穩定 | 相同元素可能跨組交換 |
public class ShellSort {
public static void shellSort(int[] arr) {
if (arr == null || arr.length <= 1) return;
int n = arr.length;
// 增量序列:n/2, n/4, ..., 1
for (int gap = n / 2; gap > 0; gap /= 2) {
// 對每個分組進行插入排序
for (int i = gap; i < n; i++) {
int key = arr[i];
int j = i - gap;
// 組內插入排序:步長為 gap
while (j >= 0 && arr[j] > key) {
arr[j + gap] = arr[j];
j -= gap;
}
arr[j + gap] = key;
}
}
}
// 測試
public static void main(String[] args) {
int[] data = {9, 5, 1, 4, 3, 8, 2, 6, 7};
shellSort(data);
System.out.println(Arrays.toString(data)); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
}
}
增量序列決定希爾排序實際效能,常見序列對比:
| 序列名稱 | 公式/定義 | 最壞時間 | 平均時間 | 備註 |
|---|---|---|---|---|
| Shell (原版) | n/2, n/4, ..., 1 | O(n²) | O(n1.5) | 簡單但非最優 |
| Hibbard | 2k - 1 (1, 3, 7, 15...) | O(n3/2) | O(n5/4) | 較 Shell 改善 |
| Sedgewick | 4k + 3·2k-1 + 1 | O(n4/3) | O(n7/6) | 實測表現最佳 |
| Pratt | 2i3j (1,2,3,4,6,8,9...) | O(n log² n) | O(n log² n) | 增量過多,實際較慢 |
| Ciura (經驗最優) | 1, 4, 10, 23, 57, 132, 301, 701... | 未知 | 極佳 | 預計算陣列,大 n 需擴展 |
public static void shellSortSedgewick(int[] arr) {
if (arr == null || arr.length <= 1) return;
int n = arr.length;
// 預計算 Sedgewick 增量 (小於 n)
int[] gaps = {1, 5, 19, 41, 109, 209, 505, 929, 2161, 3905, 8929, 16001, 36289, 64769, 146305, 260609, 587521, 1045505, 2354689, 4188161, 9427969, 16764929, 37730305, 67084289, 150958081, 268386305, 603906049, 1073643521};
// 找到第一個小於 n 的增量起始索引
int startIdx = 0;
while (startIdx < gaps.length && gaps[startIdx] >= n) startIdx++;
for (int k = startIdx; k >= 0; k--) {
int gap = gaps[k];
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;
}
}
}
// 例子:[5a, 3, 5b, 1] gap=2
// 分組0: [5a, 5b] → 排序後 [5a, 5b] (相對順序不變)
// 分組1: [3, 1] → 排序後 [1, 3]
// 合併: [5a, 1, 5b, 3] → 5a 在 5b 前
// 但 gap=1 最後排序可能交換 5a 和 5b 相對位置
跨組交換導致相同元素相對順序可能改變。
Arrays.sort() (雙軸快排/TimSort) 通常更快且穩定gap > 0,確保最後執行 gap=1j >= 0 檢查在 arr[j] 之前j -= gap,賦值時 arr[j + gap]n >>> 1 而非 n / 2 (無符號右移)