跳转至

希尔排序

✅ 一、希尔排序简介

希尔排序是插入排序的改进版本,通过引入“增量序列(gap sequence)”实现对远距离元素的预排序,逐步缩小增量,最终以 gap = 1 完成一次标准插入排序。

核心思想:

  • 将数组按一定间隔(gap)分组,对每组进行插入排序;
  • 逐渐缩小 gap,重复上述过程;
  • 最终 gap = 1 时,数组已接近有序,插入排序效率高。

✅ 二、C语言实现(多个版本)

🌟 版本1:基础希尔排序(Shell 原始增量序列:gap = n/2, n/4, ..., 1

C
// 希尔排序(Shell 增量序列:n/2, n/4, ..., 1)
void shellSort(int arr[], int n) {
    // 初始增量为数组长度的一半
    for (int gap = n / 2; gap > 0; gap /= 2) {
        // 对每个 gap 分组进行插入排序
        for (int i = gap; i < n; i++) {
            int temp = arr[i];          // 当前待插入元素
            int j = i;
            // 在当前分组中向前查找插入位置
            while (j >= gap && arr[j - gap] > temp) {
                arr[j] = arr[j - gap];  // 元素后移
                j -= gap;
            }
            arr[j] = temp;              // 插入正确位置
        }
    }
}

优点:逻辑清晰,适合初学者记忆。
缺点:最坏时间复杂度仍为 O(n²),因 Shell 序列不够高效。


🌟 版本2:优化增量序列(Knuth 序列:gap = 3*gap + 1

Knuth 提出的增量序列:1, 4, 13, 40, 121...(即 gap = 3*gap + 1),性能更优。

C
// 希尔排序(Knuth 增量序列)
void shellSortKnuth(int arr[], int n) {
    // 计算最大初始 gap(满足 gap <= n)
    int gap = 1;
    while (gap < n) {
        gap = gap * 3 + 1;  // 生成 Knuth 序列:1, 4, 13, 40...
    }

    // 开始排序,逐步缩小 gap
    for (gap = (gap - 1) / 3; gap > 0; gap = (gap - 1) / 3) {
        for (int i = gap; i < n; i++) {
            int temp = arr[i];
            int j = i;
            while (j >= gap && arr[j - gap] > temp) {
                arr[j] = arr[j - gap];
                j -= gap;
            }
            arr[j] = temp;
        }
    }
}

优点:平均性能更好,接近 O(n^1.3)。
🔁 注意:gap 先生成到略大于 n,再回退一步作为初始值。


🌟 版本3:通用希尔排序(支持任意增量序列)

C
// 使用预定义的增量序列(示例:Sedgewick 序列前几项)
void shellSortGeneric(int arr[], int n) {
    // Sedgewick 增量序列(部分): ..., 1, 5, 19, 41, 109...
    // 这里手动设置几个 gap(实际可动态生成)
    int gaps[] = {109, 41, 19, 5, 1};  // 选择适合 n 的序列
    int numGaps = sizeof(gaps) / sizeof(gaps[0]);

    for (int k = 0; k < numGaps; k++) {
        int gap = gaps[k];
        if (gap >= n) continue;  // 跳过过大的 gap

        for (int i = gap; i < n; i++) {
            int temp = arr[i];
            int j = i;
            while (j >= gap && arr[j - gap] > temp) {
                arr[j] = arr[j - gap];
                j -= gap;
            }
            arr[j] = temp;
        }
    }
}

优点:灵活性高,可用于测试不同 gap 序列效果。
🧠 Sedgewick 序列公式(部分):
- 当 k 为偶数:gap_k = 9 * 4^k - 9 * 2^k + 1
- 当 k 为奇数:gap_k = 4^k - 3 * 2^k + 1


✅ 三、完整测试程序(可运行)

C
#include <stdio.h>

void printArray(int arr[], int n) {
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    int arr[] = {64, 34, 25, 12, 22, 11, 90};
    int n = sizeof(arr) / sizeof(arr[0]);

    printf("Original array: ");
    printArray(arr, n);

    shellSort(arr, n);  // 可替换为 shellSortKnuth 或 shellSortGeneric

    printf("Sorted array: ");
    printArray(arr, n);

    return 0;
}

✅ 四、算法核心思路解析

步骤 说明
1. 选择增量序列 n/2, n/4,...,1 或 Knuth 序列
2. 分组 将数组按 gap 划分为若干子序列(索引差为 gap)
3. 组内排序 对每个子序列做插入排序
4. 缩小 gap 重复直到 gap=1
5. 最终排序 gap=1 时相当于插入排序,但此时数组已基本有序

📊 图示说明(以 gap=3 为例):

graph LR
    A[索引: 0 1 2 3 4 5 6 7 8]
    B[元素: 8 3 5 1 7 2 6 4 9]
    C[gap=3 分组]
    D["组1: 0,3,6 → 8,1,6"]
    E["组2: 1,4,7 → 3,7,4"]
    F["组3: 2,5,8 → 5,2,9"]
    C --> D
    C --> E
    C --> F

每组内部排序后,整体更有序,为后续小 gap 排序打下基础。


✅ 五、时间复杂度分析

增量序列 最坏时间复杂度 平均时间复杂度 说明
Shell 原始 (n/2, n/4...) O(n²) O(n^1.5) 简单但效率一般
Knuth ((3^k-1)/2) O(n^1.5) O(n^1.3) 更优,推荐使用
Sedgewick O(n^1.33) O(n^1.1) 目前最优之一
Hibbard O(n^1.5) O(n^1.5) 2^k - 1

📌 最好情况:O(n log n)(某些增量序列下)
📌 空间复杂度:O(1) — 原地排序
📌 稳定性:❌ 不稳定(相同元素可能因不同 gap 而改变相对顺序)


✅ 六、性能特点总结

特性 描述
适用场景 中小规模数据(< 5000),或作为快速排序的预处理
优势 实现简单、无需递归、原地排序、对部分有序数据表现好
劣势 不稳定、最坏性能差、依赖增量序列选择
比较次数 比插入排序少很多,尤其在大数据集上
移动次数 显著减少,因前期已部分排序

✅ 七、相关知识拓展

1. 增量序列(Gap Sequence)对比

序列 公式 特点
Shell ⌊n/2⌋, ⌊n/4⌋, ... 最原始,O(n²)
Knuth (3^k - 1)/2 更优,常用
Sedgewick 复杂多项式 性能最好之一
Hibbard 2^k - 1 保证 O(n^1.5)
Pratt 所有 2^p * 3^q 形式的数 O(n log²n),但 gap 太多

2. 希尔排序 vs 插入排序

对比项 插入排序 希尔排序
时间复杂度 O(n²) O(n^1.3) ~ O(n²)
适用数据 小规模、基本有序 中等规模、无序
移动次数 少(预排序)
实现难度 简单 稍复杂

3. 实际应用场景

  • 嵌入式系统或内存受限环境(无需栈空间)
  • qsort() 的早期版本中曾用作子排序
  • Linux 内核中某些模块使用(因无递归)
  • 教学用途:展示“分治+插入排序”的思想

✅ 八、变体与优化建议

  1. 动态生成增量序列
    根据 n 动态计算 Sedgewick 或 Knuth 序列,避免硬编码。

  2. 混合排序
    当 gap = 1 时,改用二分插入排序,减少比较次数。

  3. 并行化尝试
    不同 gap 分组可并行处理(但共享内存需同步,实际较少用)。

  4. 自适应增量选择
    根据数据分布动态调整 gap 序列(研究方向)。


✅ 九、记忆口诀(便于背诵)

“大步走,分组排,逐步缩,最后插”
——先大间隔分组排序,逐步缩小,最后插入收尾。


✅ 总结

项目 内容
算法类型 增量递减排序
核心思想 分组插入排序 + 增量缩小
时间复杂度 依赖增量,最佳约 O(n^1.3)
空间复杂度 O(1)
稳定性 不稳定
适用场景 中小数据、嵌入式系统、教学演示

推荐使用 Knuth 增量序列版本:平衡性能与实现复杂度,适合大多数场景。

如需更高性能,可考虑 快速排序堆排序;若数据极小,直接用 插入排序 更快。


如需我提供 动态生成 Sedgewick 序列 的 C 函数,或 可视化希尔排序过程 的代码,也可以继续提问!