希尔排序由 Donald Shell 于 1959 年发明,是第一个突破 O(n²) 时间复杂度的基于比较的排序算法。Shell 在他 1959 年的论文中提出了"增量递减"的思想,即先用较大的间隔进行粗排序,再逐渐缩小间隔进行精细排序。希尔排序是最早证明在某些情况下可以打破 O(n²) 壁垒的排序算法之一,对后续排序算法的发展产生了深远影响。
希尔排序是插入排序的推广。基本思想是:先将整个待排序序列分割成若干个子序列,分别进行插入排序;然后逐渐减小子序列之间的间隔,最终当间隔为 1 时,整个序列已经基本有序,此时进行一次普通的插入排序即可完成排序。
间隔序列的选择对希尔排序的性能影响很大。常见的间隔序列有:
public class ShellSort {
/**
* 希尔排序(使用 Shell 原始间隔序列)
*/
public static void sort(int[] arr) {
int n = arr.length;
// 初始间隔为 n/2,每次减半
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 >= gap && arr[j - gap] > key) {
arr[j] = arr[j - gap];
j -= gap;
}
arr[j] = key;
}
}
}
/**
* 希尔排序(使用 Hibbard 间隔序列)
* Hibbard 序列:1, 3, 7, 15, 31, ...
* 最坏情况时间复杂度为 O(n^(3/2))
*/
public static void sortHibbard(int[] arr) {
int n = arr.length;
// 计算最大的 Hibbard 间隔
int gap = 1;
while (gap < n / 3) {
gap = gap * 3 + 1; // 1, 4, 13, 40, 121, ...
}
while (gap > 0) {
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 = (gap - 1) / 3;
}
}
/**
* 希尔排序(使用 Sedgewick 间隔序列)
* Sedgewick 序列:1, 5, 19, 41, 109, 209, 505, 929, ...
* 实际性能通常优于 Hibbard 序列
*/
public static void sortSedgewick(int[] arr) {
int n = arr.length;
// Sedgewick 间隔序列
int[] gaps = {1, 5, 19, 41, 109, 209, 505, 929, 2161, 3905,
8929, 16001, 36289, 64769, 146305, 260609};
// 找到最大的可用间隔
int gapIndex = gaps.length - 1;
while (gapIndex >= 0 && gaps[gapIndex] >= n) {
gapIndex--;
}
while (gapIndex >= 0) {
int gap = gaps[gapIndex];
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;
}
gapIndex--;
}
}
/**
* 希尔排序(泛型版本)
*/
public static <T extends Comparable<T>> void sortGeneric(T[] arr) {
int n = arr.length;
for (int gap = n / 2; gap > 0; gap /= 2) {
for (int i = gap; i < n; i++) {
T key = arr[i];
int j = i;
while (j >= gap && arr[j - gap].compareTo(key) > 0) {
arr[j] = arr[j - gap];
j -= gap;
}
arr[j] = key;
}
}
}
}
| 间隔序列 | 最好情况 | 最坏情况 | 平均情况 |
|---|---|---|---|
| Shell 原始 (n/2ᵏ) | O(n log n) | O(n²) | O(n^(3/2)) |
| Hibbard (2ᵏ-1) | O(n log n) | O(n^(3/2)) | O(n^(5/4)) |
| Sedgewick | O(n log n) | O(n^(4/3)) | ≈ O(n^(7/6)) |
希尔排序是不稳定的排序算法(因为间隔分组导致相等元素可能被交换到不同位置),但它是原地排序。希尔排序的实际性能取决于间隔序列的选择,好的间隔序列可以使其在中等规模数据上接近 O(n log n) 的性能。