rokevin
移动
前端
语言
  • 基础

    • Linux
    • 实施
    • 版本构建
  • 应用

    • WEB服务器
    • 数据库
  • 资讯

    • 工具
    • 部署
开放平台
产品设计
  • 人工智能
  • 云计算
计算机
其它
GitHub
移动
前端
语言
  • 基础

    • Linux
    • 实施
    • 版本构建
  • 应用

    • WEB服务器
    • 数据库
  • 资讯

    • 工具
    • 部署
开放平台
产品设计
  • 人工智能
  • 云计算
计算机
其它
GitHub
  • 希尔排序

  • 算法定义
  • 算法思路
  • 算法复杂度
  • 优点与缺点
  • 应用场景
  • 各版本优化说明
  • 代码
  • 资料

希尔排序

算法定义

希尔排序(Shell Sort)是插入排序的一种改进版本,由计算机科学家希尔(Donald Shell)在 1959 年提出。它通过将数组按一定间隔(步长)分为多个子数组,对每个子数组进行插入排序,然后逐步缩小间隔,重复上述过程,直到间隔为 1,最后进行一次普通插入排序,使整个数组有序。

希尔排序的核心思想是让元素先进行远距离的交换,使数组尽快基本有序,再进行近距离交换(最终为相邻元素),从而克服插入排序在处理大规模无序数组时效率低下的问题。

算法思路

希尔排序的步骤如下:

  1. 选择初始步长:通常选择数组长度的一半(gap = n/2)作为初始步长。
  2. 分组插入排序:按当前步长gap将数组分为gap个子数组,对每个子数组执行插入排序。
  3. 缩小步长:将步长缩小(通常取当前步长的一半,gap = gap/2),重复步骤 2。
  4. 终止条件:当步长缩小到 1 时,进行最后一次插入排序(此时就是普通插入排序),排序完成。

不同的步长选择会影响希尔排序的效率,常见的步长序列有希尔初始步长(n/2, n/4, ..., 1)、Knuth 步长((3^k - 1)/2)等。

算法复杂度

  • 最好时间复杂度:O (n log n) - 取决于步长序列,部分优化步长可达到此复杂度
  • 最坏时间复杂度:O (n²) - 采用希尔初始步长时;优化步长可降至 O (n^1.5) 或更低
  • 平均时间复杂度:O (n log n) ~ O (n²) - 取决于步长序列,目前尚无精确结论
  • 空间复杂度:O (1) - 原地排序,只需常数级额外空间

优点与缺点

  • 优点:
    • 比普通插入排序效率高,尤其对中等规模数组
    • 原地排序,空间复杂度低(O (1))
    • 实现简单,易于理解和编码
    • 对几乎有序的数组效率更高
  • 缺点:
    • 不稳定的排序算法(相等元素的相对位置可能改变)
    • 性能依赖步长序列的选择,不同步长效率差异大
    • 理论分析复杂,时间复杂度难以精确证明
    • 对大规模数据,效率不如快速排序、归并排序等 O (n log n) 级算法

应用场景

  • 中等规模数据的排序(比插入排序适合更大规模数据)
  • 对空间使用有严格限制的场景(原地排序特性)
  • 嵌入式系统或内存有限的设备
  • 作为其他复杂排序算法的补充(如处理部分有序数组)

各版本优化说明

  1. 基本版(希尔步长):
    • 采用希尔提出的初始步长(n/2, n/4, ..., 1)
    • 实现简单,但步长之间存在倍数关系,可能导致某些元素重复比较
    • 最坏时间复杂度为 O (n²)
  2. Knuth 步长优化:
    • 使用 Knuth 提出的步长序列:(3^k - 1)/2(如 1, 4, 13, 40, 121...)
    • 步长之间不成倍数关系,减少了重复比较
    • 最坏时间复杂度优化至 O (n^1.5),适合中等规模数组
  3. Sedgewick 步长优化:
    • 使用 Sedgewick 提出的复合步长序列(如 1, 5, 19, 41...)
    • 经过严格测试,在实践中表现最优,平均时间复杂度接近 O (n log n)
    • 适合较大规模数组,但步长序列需要预先定义
  4. 混合插入排序优化:
    • 当步长缩小到某个阈值(如 15)时,改用普通插入排序
    • 利用插入排序在小规模数据上的高效性,减少希尔排序的迭代次数
    • 综合性能优于纯希尔排序,实现简单且实用

希尔排序是插入排序的高效改进版,通过合理选择步长序列,可以在中等规模数据排序中获得良好性能。它的实现简单且空间开销小,是实际应用中值得考虑的排序算法。

代码

import java.util.Arrays;

public class ShellSort {
    
    // 1. 基本版希尔排序(希尔初始步长:n/2, n/4, ..., 1)
    public static void shellSortBasic(int[] arr) {
        if (arr == null || arr.length <= 1) {
            return;
        }
        
        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; // 插入元素
            }
        }
    }
    
    // 2. 优化版1:Knuth步长序列((3^k - 1)/2)
    public static void shellSortKnuth(int[] arr) {
        if (arr == null || arr.length <= 1) {
            return;
        }
        
        int n = arr.length;
        int gap = 1;
        
        // 计算最大的Knuth步长(小于n/3)
        while (gap <= n / 3) {
            gap = gap * 3 + 1; // Knuth步长公式:3^k - 1 / 2
        }
        
        // 按Knuth步长排序,逐步减小步长
        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;
            }
            // 步长减小(反向应用Knuth公式)
            gap = (gap - 1) / 3;
        }
    }
    
    // 3. 优化版2:Sedgewick步长序列(更高效的步长)
    public static void shellSortSedgewick(int[] arr) {
        if (arr == null || arr.length <= 1) {
            return;
        }
        
        int n = arr.length;
        
        // 生成Sedgewick步长序列(适合n < 10^6)
        int[] sedgewickGaps = {1, 5, 19, 41, 109, 209, 505, 929, 2161, 3905, 8929, 
                             16001, 36289, 64769, 146305, 260609};
        
        // 找到适合当前数组长度的最大步长
        int gapIndex = 0;
        while (gapIndex < sedgewickGaps.length && sedgewickGaps[gapIndex] < n) {
            gapIndex++;
        }
        gapIndex--;
        
        // 按Sedgewick步长排序
        while (gapIndex >= 0) {
            int gap = sedgewickGaps[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--;
        }
    }
    
    // 4. 优化版3:对小规模子数组使用插入排序
    public static void shellSortHybrid(int[] arr) {
        if (arr == null || arr.length <= 1) {
            return;
        }
        
        int n = arr.length;
        final int INSERTION_THRESHOLD = 15; // 子数组长度阈值
        
        // 希尔排序主循环
        for (int gap = n / 2; gap > INSERTION_THRESHOLD; gap /= 2) {
            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;
            }
        }
        
        // 当步长小于阈值时,直接使用插入排序
        if (n > INSERTION_THRESHOLD) {
            insertionSort(arr, 0, n - 1);
        }
    }
    
    // 辅助插入排序(用于混合版本)
    private static void insertionSort(int[] arr, int left, int right) {
        for (int i = left + 1; i <= right; i++) {
            int key = arr[i];
            int j = i - 1;
            
            while (j >= left && arr[j] > key) {
                arr[j + 1] = arr[j];
                j--;
            }
            arr[j + 1] = key;
        }
    }
    
    public static void main(String[] args) {
        int[] arr = {64, 34, 25, 12, 22, 11, 90, 5, 1, 8, 7, 6, 4, 3, 2};
        int[] arr1 = Arrays.copyOf(arr, arr.length);
        int[] arr2 = Arrays.copyOf(arr, arr.length);
        int[] arr3 = Arrays.copyOf(arr, arr.length);
        int[] arr4 = Arrays.copyOf(arr, arr.length);
        
        shellSortBasic(arr1);
        System.out.println("基本版(希尔步长): " + Arrays.toString(arr1));
        
        shellSortKnuth(arr2);
        System.out.println("Knuth步长: " + Arrays.toString(arr2));
        
        shellSortSedgewick(arr3);
        System.out.println("Sedgewick步长: " + Arrays.toString(arr3));
        
        shellSortHybrid(arr4);
        System.out.println("混合插入排序: " + Arrays.toString(arr4));
    }
}

资料

https://blog.csdn.net/weixin_38333555/article/details/80515605

最近更新:: 2025/9/8 23:27
Contributors: luokaiwen