随笔 - 88  文章 - 0  评论 - 12  阅读 - 84003

十大经典排序算法 ( 四 ) 希尔排序

介绍 : 

  希尔排序(Shell's Sort)是插入排序的一种又称 “ 缩小增量排序 ”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因 D.L.Shell 于 1959 年提出而得名。

  希尔排序是基于插入排序的以下两点性质而提出改进方法的:
  1. 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。
  2. 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位。

算法原理 :

  先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前两种方法有较大提高。

动图演示 :

算法稳定性 :

  由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。

时间复杂度 :
  希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小,插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比 o( n^2 ) 好一些。

  1.增量序列的选择
    Shell排序的执行时间依赖于增量序列。
    好的增量序列的共同特征:
      ① 最后一个增量必须为1;
      ② 应该尽量避免序列中的值(尤其是相邻的值)互为倍数的情况。
    有人通过大量的实验,给出了较好的结果:当n较大时,比较和移动的次数约在n^1.25到(1.6n)^1.25之间。
  2.Shell排序的时间性能优于直接插入排序
    希尔排序的时间性能优于直接插入排序的原因:
      ①当文件初态基本有序时直接插入排序所需的比较和移动次数均较少。
      ②当n值较小时,n 和  的差别也较小,即直接插入排序的最好时间复杂度O(n)和最坏时间复杂度 0( ) 差别不大。
      ③在希尔排序开始时增量较大,分组较多,每组的记录数目少,故各组内直接插入较快,后来增量di逐渐缩小,分组数逐渐减少,而各组的记录数目逐渐增多,但由于已经按di-1作为距离排过序,使文件较接近于有序状态,所以新的一趟排序过程也较快。
 
  因此,希尔排序在效率上较直接插入排序有较大的改进。

  希尔排序的复杂度和增量序列是相关的

  {1,2,4,8,...}这种序列并不是很好的增量序列,使用这个增量序列的时间复杂度(最坏情形)是O(n^2)

  Hibbard提出了另一个增量序列{1,3,7,...,2^k-1},这种序列的时间复杂度(最坏情形)为O(n^1.5)

  Sedgewick提出了几种增量序列,其最坏情形运行时间为O(n^1.3),其中最好的一个序列是{1,5,19,41,109,...}

应用场景 :

  专家们提倡,几乎任何排序工作在开始时都可以用希尔排序,若在实际使用中证明它不够快,再改成快速排序这样更高级的排序算法。因为对于中等大小的数组它的运行时间是可以接受的。 它的代码量很小,且不需要使用额外的内存空间。虽然有更加高效的算法,但 除了对于很大的 N,它们可能只会比希尔排序快两倍(可能还达不到),而且更复杂。如果你需要 解决一个排序问题而又没有系统排序函数可用(例如直接接触硬件或是运行于嵌入式系统中的代 码),可以先用希尔排序,然后再考虑是否值得将它替换为更加复杂的排序算法。

Java代码 :

复制代码
/**
 * 希尔排序 : 把数组按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的元素越来越多,当增量减至 1 时,整个数组恰被分成一组,算法便终止。
 */
public class ShellSort {

    public static void sort(int[] arr) {
        // 判断边界条件
        if (arr == null || arr.length < 2) {
            return;
        }
        int gap = 1;// 增量
        while (gap < arr.length) {
            gap = gap * 5 + 1;// 增量序列 : 1 , 4 , 13 , 40 , 121 , 364 , 1093...
        }
        // 进行分组
        while (gap > 0) {
            // 对各个分组进行 插入排序
            for (int i = gap; i < arr.length; i++) {
                insertValue (arr, gap, i);// 将arr[i]插入到所在分组的正确位置上
            }
            gap = gap / 5;// gap收缩,直至1
        }
    }

    /**
     * 将arr[i]插入到所在分组的正确位置上 ( 代码与插入排序几乎一致 )
     *
     * @param arr 数组
     * @param gap 增量
     * @param i   数组下标
     */
    private static void insertValue(int[] arr, int gap, int i) {
        int tmp = arr[i];
        int j = i - gap;
        // 按组进行插入(组内元素两两相隔gap)
        while (j >= 0 && arr[j] > tmp) {
            arr[j + gap] = arr[j];
            j -= gap;
            if (j + gap != i) {
                arr[j + gap] = tmp;
            }
        }
    }
}
复制代码

 

如果还有没看懂的同学 , 可以看看下面这个有大量图解的博客 

https://blog.csdn.net/qq_39207948/article/details/80006224

posted on   法哈席基  阅读(765)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示