内容介绍
希尔排序简介
冒泡排序在1956年就已经被研究。可是排序速度是比较慢的,冒泡排序的时间复杂度是O(n^2),然而在后面的很长的时间里,尽管人们发明各类排序算法(好比前面选择排序和插入排序),但时间复杂度都是0(n^2),时间复杂度彷佛无法超越0(n^2)。此时,计算机学术界充斥着“排序算法不可能突破O(n^2)的声音”。终于有一天,当一位科学家发布超越了0(n^2)新排序算法后,紧接着就出现了好几种能够超越0(n^2)的排序算法,并把内排序算法的时间复杂度提高到了0(nlogn)。 “不可能超越0(n^2)”完全成为了历史。java
希尔排序按其设计者希尔(Donald Shell)的名字命名,该算法由1959年公布。希尔排序也是一种插入排序,它是简单插入排序通过改进以后的一个更高效的版本,也称为缩小增量排序,在这以前排序算法的时间复杂度基本都是0(n^2)的,希尔排序算法是突破这个时间复杂度的第一批算法之一。算法
前面讲的插入排序,它的效率在数据近乎有序时候是很高的,只须要少许的插入操做,就能够完成整个排序工做,此时直接插入很高效。还有就是数据比较少时,直接插入的优点也比较明显。可问题在于,两个条件自己就过于苛刻,实际开发中数据少或者基本有序都属于特殊状况。shell
不过别急,条件不存在,咱们创造条件也是能够去作的。希尔排序(Shell'sSort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。该方法因D.L.Shell于1959年提出而得名。虽然插入排序是O(n^2)的排序复杂度,可是若是已是有序的,则能够提早结束,能够提升效率咱们知道插入排序在近乎有序的数组中效率很高, 如何让待排序的记录数量少而且近乎有序呢?很容易想到的就是将本来有大量记录数的记录进行分组。分割成若干个子序列,此时每一个子序列待排序的记录个数就比较少了,而后在这些子序列内分别进行直接插入排序,当整个序列都基本有序时,注意只是基本有序时,再对全体记录进行一次直接插入排序。编程
有同窗会疑惑。这不对呀,好比咱们如今有个序列是{9, 1, 5, 8, 3, 7, 4, 6, 2},如今将它分红三组:{9, 1, 5},{8, 3, 7},{4, 6, 2},哪怕将它们各自排序排好了,变成{1, 5, 9},{3, 7, 8},{2,4,6}再合并它们成{1, 5, 9, 3, 7, 8, 2, 4, 6},此时,这个序列仍是杂乱无序,谈不上基本有序,要排序仍是重来一遍直接插入有序,这样作没有用。须要强调一下,所谓的基本有序,就是小的关键字基本在前面,大的基本在后面,不大不小的基本在中间,像{2, 1, 3, 6, 4, 7, 5, 8, 9}这样 能够称为基本有序了。但像{1, 5, 9, 3, 7, 8, 2, 4, 6} 这样的9在第三位,2在倒数第三位就谈不上基本有序。问题其实也就在这里,咱们分割待排序记录的目的是减小待排序记录的个数,并使整个序列向基本有序发展。而像上面这样分完组后就各自排序的方法达不到咱们的要求。所以,咱们须要采起跳跃分割的策略:将相距某个“增量” 的记录组成一一个子序列,这样才能保证在子序列内分别进行直接插入排序后获得的结果是基本有序而不是局部有序。数组
希尔排序的思想
希尔排序,经过增量(gap)将元素两两分组,对每组使用直接插入排序算法排序;增量(gap)逐渐减小,当增量(gap)减至1时,整个数据恰被分红一组,最后进行一次插入排序,整个数组就有序了。微信
希尔排序动画演示
通常没有特殊要求排序算法都是升序排序,小的在前,大的在后。 数组由{7, 3, 1, 9, 5, 4, 2, 8, 6} 这9个无序元素组成。学习
第一次:gap = 9/2 = 4 动画: 动画
第二次:gap = 4/2 = 2 动画: 设计
第三次:gap = 2/2 = 1 动画: 3d
希尔排序分析
希尔排序,经过增量(gap)将元素分红n组,对每组使用直接插入排序算法排序。增量(gap)逐渐减小,当增量(gap)减至1时,整个数据恰被分红一组,最后进行一次插入排序。
第一次:gap = 9/2 = 4,每一个元素和后面的元素之间间隔4,元素被分红4组。其中元素7, 5, 6分红一组,元素3,4分红一组,元素1, 2分红一组,元素9,8分红一组,效果以下图:
第二次:gap = 4/2 = 2,每一个元素和后面的元素之间间隔2,元素被分红2组。
第三次:gap = 2/2 = 1,每一个元素和后面的元素之间间隔1,元素被分红1组。
希尔排序代码编写
回顾一下刚才的分析,当咱们有9个元素时,gap分别取值4, 2, 1。
元素数量不一样gap取值也不一样,须要一个循环控制gap的取值。当取好gap后须要使用插入排序对每组进行排序,须要使用嵌套循环。 在进行小范围插入排序时,要注意从哪一个位置开始。咱们先回顾一下以前学习的插入排序,第一个元素6
不须要动,从第二元素5
开始才须要进行比较和插入。
希尔排序拆分红多个小组后,每一个小组使用插入排序,也是从每一个小组的第二个数据开始比较插入,以下图所示:
从上图中能够看出,第一组从索引4开始比较,第二组从索引5开始比较,第三组从索引6开始比较,第四组从索引7开始比较。
希尔排序代码以下:
public class ShellSortTest2 { public static void main(String[] args) { int[] arr = new int[] {7, 3, 1, 9, 5, 4, 2, 8, 6}; shellSort(arr); } public static void shellSort(int[] arr) { // 增量gap,经过gap来划分出不一样的子序列,gap不断减小,划分的子序列更多 for (int gap = arr.length / 2; gap > 0; gap /= 2) { // 循环获得不一样的gap,gap = 4, 2, 1 System.out.println("gap: " + gap); for (int i = gap; i < arr.length; i++) { // 每组使用插入排序 for (int j = i; j-gap >= 0; j -= gap) { if (arr[j-gap] > arr[j]) { swap(arr, j-gap, j); System.out.print("\t交换: " + arr[j-gap] + "和" + arr[j]); System.out.println(Arrays.toString(arr)); } } } } } public static void swap(int[] arr, int start, int end) { int temp = arr[start]; arr[start] = arr[end]; arr[end] = temp; } }
执行效果:
gap: 4 交换: 5和7[5, 3, 1, 9, 7, 4, 2, 8, 6] 交换: 8和9[5, 3, 1, 8, 7, 4, 2, 9, 6] 交换: 6和7[5, 3, 1, 8, 6, 4, 2, 9, 7] gap: 2 交换: 1和5[1, 3, 5, 8, 6, 4, 2, 9, 7] 交换: 4和8[1, 3, 5, 4, 6, 8, 2, 9, 7] 交换: 2和6[1, 3, 5, 4, 2, 8, 6, 9, 7] 交换: 2和5[1, 3, 2, 4, 5, 8, 6, 9, 7] gap: 1 交换: 2和3[1, 2, 3, 4, 5, 8, 6, 9, 7] 交换: 6和8[1, 2, 3, 4, 5, 6, 8, 9, 7] 交换: 7和9[1, 2, 3, 4, 5, 6, 8, 7, 9] 交换: 7和8[1, 2, 3, 4, 5, 6, 7, 8, 9]
希尔排序的增量和复杂度
经过上面动画和代码,相信你们有些明白,希尔排序的关键并非随便分组后各自排序,而是将相隔某个“增量”的记录组成一个子序列,实现跳跃式的移动,使得排序的效率提升。这里"增量”的选取就很是关键了。咱们在代码中是用int gap = arr.length / 2;
的方式选取增量的,可究竟应该选取什么样的增量才是最好,目前仍是一个数学难题,迄今为止尚未人找到一种最好的增量序列。
迄今为止,除了在一些特殊的状况下,尚未人可以从理论上分析希尔排序的效率。有各类各样基于试验的评估,估计它的时间复杂度从0(N^3/2)到0(N^7/6)。
希尔排序常见的增量序列:
-
Shell 增量序列,Shell 增量序列的递推公式为:
-
Hibbard 增量序列通项公式为:
-
Knuth 增量序列通项公式为:
-
Gonnet 增量序列递推公式为:
-
Sedgewick 增量序列通项公式为:
须要注意的是,增量序列的最后一个增量值必须等于1才行,最后一趟排序是一次普通的插入排序。希尔排序算法的发明,使得咱们终于突破了慢速排序以后,更为高效的排序算法也就相继出现了。
总结
希尔排序,是直接插入排序算法的一种更高效的改进版本。经过增量(gap)将元素进行分组,对每组使用直接插入排序算法排序;增量(gap)逐渐减小,当增量(gap)减至1时,整个数据恰被分红一组,最后进行一次插入排序。
原创文章和动画制做真心不易,您的点赞就是最大的支持! 想了解更多文章请关注微信公众号:表哥动画学编程