详解十大经典排序算法(四):希尔排序(Shell Sort)

算法原理

希尔排序是一种基于插入排序的排序算法,也被称为缩小增量排序。它通过将待排序的序列分割成若干个子序列,对每个子序列进行插入排序,然后逐步缩小增量,最终使整个序列有序。

算法描述

希尔排序(Shell Sort)是一种基于插入排序的算法,由Donald Shell于1959年提出。它是插入排序的一种更高效的改进版本。希尔排序的核心思想是将原始数据分成多个子序列,先对每个子序列进行插入排序,然后逐步减少子序列的数量,最终对整个数据序列进行插入排序。

这个算法的关键特点是其间隔序列的选择,即每次排序时元素之间的间隔。开始时,间隔较大,随着算法的进行,间隔逐渐减小,最后间隔为1,即普通的插入排序。通过这种方式,希尔排序可以快速减少大量的初期乱序,从而提高排序效率。

希尔排序的步骤如下:

  1. 选择合适的间隔序列:最初的版本使用序列长度的一半作为第一个间隔,然后逐步减半,直到间隔为1。现代版本可能会使用不同的间隔序列。

  2. 分组并排序:按照当前间隔将数组分为多个子序列,独立地对每个子序列应用插入排序。

  3. 减少间隔并重复:减少间隔大小,重复上述过程,直到间隔减至1。

  4. 最终排序:当间隔减至1时,进行一次标准的插入排序,此时由于序列已经部分排序,这一步骤会非常快。

动画演示

在这里插入图片描述

代码实现

public static void shellSort(int arr[]) {
    
    
        int n = arr.length;

        //设置排序的间隔(gap)。
        //初始间隔设置为数组长度的一半,每次循环后间隔减半。
        //循环继续直到间隔减至0。
        for (int gap = n / 2; gap > 0; gap /= 2) {
    
    

            //内层循环从gap开始,对数组进行遍历。
            for (int i = gap; i < n; i += 1) {
    
    
                int temp = arr[i];//存储当前元素的值。
                int j;//用于后续的内嵌循环,用于比较和交换元素。

                //此循环用于将较大的元素向右移动。
                //当j大于等于gap且arr[j - gap](当前元素的前一个间隔位置的元素)大于temp(当前元素)时,将arr[j - gap]向右移动到arr[j]的位置。
                for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
    
    
                    arr[j] = arr[j - gap];
                }
                //此处的j 是上面循环 j -= gap 后的。
                arr[j] = temp;
            }
        }


    }

算法复杂度

时间复杂度(最坏) 时间复杂度(最好) 时间复杂度(平均) 空间复杂度 稳定性
O(n^2) O(nlogn) O(n^1.5) O(1) 不稳定
  • 最好情况:O(nlogn)
  • 平均情况:取决于间隔序列。对于原始的间隔序列(减半序列),平均时间复杂度为O(n^1.5)。
  • 最坏情况:取决于间隔序列。对于某些不佳的间隔序列,最坏情况可以达到O(n^2)。
  • 空间复杂度:O(1)。希尔排序是一种原地排序算法。

希尔排序的优化方式

<<<前方高能 量力而行>>>

希尔排序的性能在很大程度上依赖于间隔序列的选择。不同的间隔序列会导致算法的性能有显著差异。优化希尔排序通常涉及使用更有效的间隔序列,而不是初始版本中的简单递减序列(如数组长度的一半递减至1)。以下是几种常见的优化方法:

  1. 优化间隔序列
  • Hibbard序列:使用形式为 2^k - 1 的间隔,如 1, 3, 7, 15, 31, … 这样的序列可以使希尔排序达到大约 O(n^(3/2)) 的时间复杂度。

  • Sedgewick序列:这是一种更复杂的间隔序列,可以进一步优化性能。一个常见的Sedgewick序列是 1, 5, 19, 41, 109, …

  • Knuth序列:使用形式为 (3^k - 1) / 2 的间隔,如 1, 4, 13, 40, 121, …

这些序列都是试图在排序过程中更有效地打乱数组,从而避免某些特定情况下的糟糕性能。

  1. 动态间隔

一种更高级的优化是根据数组的特定特征动态选择间隔。这种方法通常适用于特定类型的数据或在需要极致优化时使用。

下面是一个使用Hibbard间隔序列的希尔排序的Java实现示例:

 public static void sort(int[] arr) {
    
    
        int n = arr.length;
        int h = 1;

        // 计算最大间隔
        while ((h * 2) - 1 < n) {
    
    
            h = h * 2;
        }
        h = h - 1;

        // 希尔排序
        while (h >= 1) {
    
    
            for (int i = h; i < n; i++) {
    
    
                int temp = arr[i];
                int j;
                for (j = i; j >= h && arr[j - h] > temp; j -= h) {
    
    
                    arr[j] = arr[j - h];
                }
                arr[j] = temp;
            }
            h = (h + 1) / 2 - 1; // 更新间隔为下一个较小的Hibbard数
        }
    }

这段代码首先计算最大的Hibbard间隔,然后在每次迭代中减小间隔,直到间隔为1。每个间隔都会对数组进行一轮希尔排序。这种使用Hibbard间隔的方法通常比简单的递减间隔方法表现得更好。


相关概念
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。

猜你喜欢

转载自blog.csdn.net/WriteBug001/article/details/134777032