vlambda博客
学习文章列表

希尔排序算法拆解解析

1
先让我们回顾下插入排序


//比如有组数据:int[] arr = {8, 6, 2, 3, 1, 5, 7, 4};


文字概述: 假设第一个元素为最小数字。那么我们就要从index=1开始往后循环,依次拿后面的数字,与前面的数字依次比较,找出最小数字。也就是当index =1,那就是 arr[1] 和 arr[0] 比较。当index = 2 时,就是用 arr[2] 与 arr[1] 比较得出一个最小值,然后再与 arr[0] 比较。最终完成从小到大的排序。


所以代码如下


 //{8, 6, 2, 3, 1, 5, 7, 4} public void inseartSort(int[] arr) { //所以外层循环,我们跳过第一个元素 8,拿后面的数和前面的数比较 for (int i = 1; i < arr.length; i++) { //1、这层的循环是拿后面的数和前面的【依次比较】得出最小数 //2、下面内层循环不知道怎么写,我们可以先来拆分下逻辑 // (1)先看外层循环,index = 1,我们要拿arr[1]与arr[0]比较,较小的数字排在前面 // (2)继续看外层循环,index = 2,我们首先要拿arr[2]和arr[1]作比较,得出最小数后,继续和arr[0]做比较。 // 相当于index--了。 // (3)综上所述:外层循环的起始点,就是内层循环的起始点,其次index--才能依次和前面的数比较,做好排序。 // 既然是index--,那么index要满足的条件是index > 0 。不然异常。所以代码如下 for (int j = i; j > 0; j--) { if (arr[j] < arr[j - 1]) { int temp = arr[j - 1]; arr[j - 1] = arr[j]; arr[j] = temp; } } } }




2
希尔排序



了解过插入排序算法的都知道。一组很长的数据里。如果局部有序数字越多,插入排序越高效,性能越好。

希尔算法:就是利用了这个插入排序的特点改进的。他这里有个增量gap的概念。我们暂且不去探讨增量序列带来的时间复杂度。


假如还是那组数据:{8, 6, 2, 3, 1, 5, 7, 4}。假设这里的增量是 gap = arr.length/2。也就是4,他会对数组,逻辑分组成几份(这里也能看成,增量是几就是分几份),然后依次对逻辑分组里进行插入排序,其数组还是同一个数组。排序好之后再用 gap /= 2。也就是2。继续逻辑分组,继续插入排序。这样原始数组内大部分数据都是有序数据。最后 gap = 1,对这大部分数据都是有序数据的数组进行插入排序。那么速度就快很多,性能方便也很高效。


可能这么说很笼统,请看剖析


gap=4 时 那么就是

  • arr[0]和arr[4] 一组

  • arr[1]和arr[5] 一组

  • arr[2]和arr[6] 一组

  • arr[3]和arr[7] 一组



这样是逻辑分组,

  •  8和1排序后: 1 8;

  • 5和6排序后: 5 6;

  • 2和7排序后: 2 7;

  • 3和4排序后: 3 4。


希尔排序算法拆解解析

竖着看,此时排序得到:1 5 2 3 8 6 7 4



gap = 2 时


gap为2时就相隔2,去分组,当然也就是分成2组

希尔排序算法拆解解析

gap=2时的分组为

  • 1 2 8 7;排序后得到 1 2 7 8

  • 5 3 6 4;排序后得到 3 4 5 6


那么就是

竖着看,此时排序得到:1 3 2 4 7 5 8 6


最后gap = 1的时候就是正常的插入排序了。可能有人说,按着增量做,最后一组数据并没有大部分数字有序啊。因为为了讲解选了比较少的数据size=8的数据。如果是size>=100000的话会非常的明显。



3
希尔排序的java表达


通过上面的讲解,我们知道。其实希尔排序多了个增量的概念,大体没有变化


首先我们知道增量gap的变化

//不难理解,gap最终变化是4,2,1for (int gap = arr.length / 2; gap > 0; gap /= 2) {}



接下来我们把gap=1时,也就是正常插入排序的代码直接搬进去,如下

for (int gap = arr.length / 2; gap > 0; gap /= 2) { for (int i = 1; i < arr.length; i++) { for (int j = i; j > 0; j--) { if (arr[j] < arr[j - 1]) { int temp = arr[j - 1]; arr[j - 1] = arr[j]; arr[j] = temp; } } } }      //上面式子看不大出来,我们把它变一下如下 //看看哪里时候把gap加上 for (int gap = arr.length / 2; gap > 0; gap /= 2) { for (int i = 1; i < arr.length; i++) { for (int j = i; j >= 1; j-=1) { //主要是修改了j>0 改成了 j>=1。j-- 改成了 j-=1 if (arr[j] < arr[j - 1]) { int temp = arr[j - 1]; arr[j - 1] = arr[j]; arr[j] = temp; } } } }     //改完之后,我们把gap=1的情况改成gap。也就是把1改成gap变化如下 for (int gap = arr.length / 2; gap > 0; gap /= 2) { for (int i = gap; i < arr.length; i++) { for (int j = i; j >= gap; j -= gap) { if (arr[j] < arr[j - gap]) { int temp = arr[j - gap]; arr[j - gap] = arr[j]; arr[j] = temp; } } } }


好了,希尔排序就是这样了!接下来我们打印下,来验证最开始的逻辑: