希尔排序

80 阅读3分钟

希尔排序是对直接插入排序的一种高效改进,它通过引入“增量”的概念,对元素进行分组和预处理,巧妙地提升了排序效率。

下面我将通过一个例子、一个表格、一段代码,帮你彻底掌握它。

📃 核心思想与执行步骤

希尔排序的核心思想是:先将整个待排序的序列分割成若干子序列,分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序

希尔排序.gif

具体步骤如下:

  1. 选择增量:首先确定一个初始增量(gap),通常为数组长度的一半。
  2. 分组排序:将序列中间隔为 gap的元素分成一组,对每一组进行直接插入排序。
  3. 缩小增量:减小 gap的值(例如,gap = gap / 2),重复第2步,直到 gap为 1。
  4. 最终排序:当 gap为 1 时,整个序列作为一组进行最后一次直接插入排序,此时由于序列已经基本有序,这次排序会非常高效。

🧮 增量序列的选择

增量的选择方案对希尔排序的性能至关重要。下表对比了两种常见的增量序列取法:

增量序列类型计算方式特点说明
Shell 原始序列初始增量:gap = n/2 后续增量:gap = gap / 2实现简单,易于理解,但效率并非最优。
Knuth 序列初始增量:h = 1 后续计算:h = 3*h + 1,直到 h > n/3,然后增量反向计算:gap = h / 3通常能获得更好的排序效率,是实践中常用的高效序列之一。

💻 Java 代码实现

以下是使用 Knuth 序列实现的希尔排序(升序)示例代码,关键步骤配有详细注释:

public class ShellSort {

    public static void shellSort(int[] arr) {
        if (arr == null || arr.length <= 1) {
            return;
        }

        int n = arr.length;
        
        // 1. 计算初始增量(Knuth序列)
        int h = 1;
        while (h <= n / 3) {
            h = 3 * h + 1; // 1, 4, 13, 40, 121, ...
        }

        // 2. 开始排序,增量逐步缩小
        while (h >= 1) {
            // 3. 对每个子序列进行插入排序,从第h个元素开始
            for (int i = h; i < n; i++) {
                int temp = arr[i]; // 当前待插入元素
                int j = i;
                // 4. 在子序列中寻找插入位置
                while (j >= h && arr[j - h] > temp) {
                    arr[j] = arr[j - h]; // 元素后移
                    j -= h;
                }
                arr[j] = temp; // 插入到正确位置
            }
            // 5. 缩小增量
            h = h / 3;
        }
    }

    public static void main(String[] args) {
        int[] arr = {9, 1, 5, 8, 3, 4, 6, 10, 2, 12};
        System.out.println("排序前: " + java.util.Arrays.toString(arr));
        
        shellSort(arr);
        
        System.out.println("排序后: " + java.util.Arrays.toString(arr));
    }
}

⚙ 算法特性分析

  • 时间复杂度:希尔排序的时间复杂度是增量序列的函数,并非一个定值。使用 Knuth 序列时,平均时间复杂度约为 O(n^1.5) ,优于普通插入排序的 O(n²)。最好情况下可接近 O(n log n),最坏情况下为 O(n²)。
  • 空间复杂度:排序过程仅需常数个额外空间(如变量 temp, h等),因此空间复杂度为 O(1)
  • 稳定性:由于相等元素可能在不同的增量分组排序过程中被移动,导致相对顺序改变,因此希尔排序是不稳定的排序算法。

💎 总结与适用场景

希尔排序是对直接插入排序的重要改进,它通过前期的大步长调整使元素大幅跨距离移动,快速趋于有序,减少了后期小步长排序的工作量

它特别适用于中等规模数据的排序,在数据量不是特别巨大时,其性能表现往往优于更复杂的排序算法。同时,由于其代码量适中且是原地排序(空间复杂度 O(1)),在一些内存空间有限的嵌入式系统或特定场景下也很有价值。