vlambda博客
学习文章列表

【经典排序】希尔排序


希尔排序


简介:

    希尔排序,英文Shell's sort,是一位叫D.L.Shell的老哥在1959就提出来的算法,是《》的一种又称“缩小增量排序”(Diminishing Increment Sort),其实就是是直接插入排序算法的一种高效的改进版本。

    希尔排序是非稳定排序算法。


原理:

    大的原理跟《》原理一样,都是后一位跟前一位比较大小,后一位小的就交换位置。

    优化在于,这里增加了一个叫“量”的东西,其实就是分组的大小,然后每次开始,先把所有数组分成 “增量m” 个分组,当然每个分组的大小是增量n/m。然后每次增量m除以2,到最后为1(整个数组)结束。


思路:

  1. 这里假设开始设置一个增量n/2,

  2. 将所有数据分组,每组就是2个数(n为偶数的话),分组个数就是n/2个,n是数组长度。

  3. 然后这个几个分组在组内进行插入排序。

  4. 增量m=m/2,也就是分组数量减半,合并了,继续在变大了的分组进行插入排序。

  5. 重复步骤②③④,知道最后m=1,也就是只有一个分组,所有元素在一组,进行一次插入排序。

  6. 结束。


上图上图↓


示例数组[1,10,35,61,89,36,55,44]

第一轮,初始增量8除以2,为4


【经典排序】希尔排序


第一轮后数组[1,10,35,44,89,36,55,61]

第二轮增量4除以2,为2


【经典排序】希尔排序


第二轮后数组[1,10,35,36,55,44,89,61]

第三轮增量2除以2,为1

【经典排序】希尔排序


最后数组[1,10,35,36,44,55,61,89]。


    

表格形式展示↓




代码:

public static void sort3(int[] a) {    //增量 int gap = a.length; do { gap /= 2; //增量每次减半 for (int i = 0; i < gap; i++) {            // 插入排序 for (int j = i; j < a.length; j += gap) { int k; int t = a[j]; for (k = j; k - gap >= 0 && t < a[k - gap]; k -= gap) { a[k] = a[k - gap]; } a[k] = t; } } } while (gap != 1);}


算法分析:


稳定性:

    由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序。但是上面排序也可以看出,分组分轮进行排序的时候,不同的轮中相同的数,就可以发生次序变化,前后互换,最后其稳定性就会被打乱,所以shell排序是不稳定的。


时间复杂度:

    希尔排序是基于插入排序的一种算法, 在此算法基础之上增加了一个新的特性,提高了效率。希尔排序的时间的时间复杂度为O(n^(3/2)),希尔排序时间复杂度的下界是n*log2n。希尔排序没有快速排序算法快 O(n(logn)),因此中等大小规模表现良好,对规模非常大的数据排序不是最优选择。但是比O(n²)复杂度的算法快得多。并且希尔排序非常容易实现,算法代码短而简单。 

    此外,希尔算法在最坏的情况下和平均情况下执行效率相差不是很多,与此同时快速排序在最坏的情况下执行的效率会非常差。专家们提倡,几乎任何排序工作在开始时都可以用希尔排序,若在实际使用中证明它不够快,再改成快速排序这样更高级的排序算法. 本质上讲,希尔排序算法是直接插入排序算法的一种改进,减少了其复制的次数,速度要快很多。原因是,当n值很大时数据项每一趟排序需要移动的个数很少,但数据项的距离很长。当n值减小时每一趟需要移动的数据增多,此时已经接近于它们排序后的最终位置。正是这两种情况的结合才使希尔排序效率比插入排序高很多。

    Shell算法的性能与所选取的分组长度序列有很大关系。


空间复杂度:

    O(1),跟插入排序一样,是一个原地排序算法。


总结:

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




我预两副棺材,一副你的一副我的