简介
希尔排序,又称缩小增量法。
过程:
先取定一个整数d1<n,把全部结点分成d1个组,所有距离为d1倍数的记录放在一组中,在各组内进行直接插入排序;然后取d2<d1,重复上述分组和排序工作,直至取di=1,即所有记录放在一个组中排序为止。
嗯,好了,上面的看一下就得了,不然就和我一样了,当我看明白了这行字的时候,感觉自己被学校ppt演的死去活来。。
其实就是选择一串数组,比如531,只要第一个小于数组长度且最后一个为1就行了。
然后要做的,就是按照数字分组:
- a[0] a[5] a[10] a[15]……
- a[1] a[6] a[11] a[16]……
- a[2] a[7] a[12] a[17]……
- a[3] a[8] a[13] a[18]……
- a[4] a[9] a[14] a[19]……
每一组都是相差五个,所以产生了五组。
接下来就是将这五组数据排序好(使用的是插入排序)
下一个数字是3,所以按照间隔为3再次分组,排序,然后是1,就是一个完全的插入排序了。
那么可能会问,这怎么能实现高效呢,中间那么多次的插入。
其实在每一次的排序中,都是一小块一小块处理的,这样在处理大块的时候由于基本有序,所以插入排序还是很快的(上一篇提过,插入排序在基本有序的情况下是非常快的)
增量选择
增量,顾名思义指的就是上文提到的数组,看到上面的思路应该就知道了这个东西的重要性,甚至说增量的选择和这个算法一样重要,目前学到的最快的增量序列是1,5,19,41,109……
增量和算法的时间复杂度息息相关,该算法的时间复杂度范围为n*log2n到n2之间,n在一定范围内,时间复杂度为n1.3
图中是一些巨佬们提出的一些序列。
代码实现
有人看到这就会问了,博主博主,你上面说了这么多种,现在你要讲的是哪种啊。
这个东西啊,讲一种剩下的就可以自己来了,所以,我就讲最简单的那种吧(咳咳,懂的都懂)
这里采用的是 n/2 开始,然后每一次都除二直到为1。
其中数组a[0]项并没有存储数值。
void Shellsort(int* a,int n)//数组&多少个数
{
for(int k = n/2; k >= 1; k /= 2)//增量大小为除半
{
for(int i = k+1; i <= n; i++)//往后推保证了插入的顺序,同时能够遍历
{
int x = a[i];
int j = i-k;
while(j>0 && x<a[j])
{
a[j+k] = a[j];
j -= k;
}
a[j+k] = x;//组内排序,将x直接插入到组内合适的位置
}
}
}
这里面和刚才说的还是有所出入,至少不是将所有的分组都拿出来排序然后塞回去,
而是利用遍历直接将所有的组同时排好序:
- 从第一组的第二个出发(因为插入算法就是从第二个元素开始的),即a[k+1];
- 当数组下标 j 为正的时候(因为没有监视哨,所以每一次都要看一下是否越界)并且需要后移,就将j+k的位置上放上j,其实说白了就是用增量k替换了原来的1;
- 随后i++,同时j++,进入下一组;、
- 当到达第一组第三个数据,一切同上。
这个函数看起来还是有一些不好理解,单纯靠这个很难反推原来的想法,所以还是先看懂上面的思路吧(没错我当时也踩了这个雷,原地起飞,还好最后CSDN大佬捞我一把)
分析
希尔排序的时间复杂度在上面说过了,一言难尽,也是不确定的,但是空间复杂度却很好判断,因为没有使用和n有关的空间(比如数组),所以仍为o(1)。
另外,希尔排序是不稳定的,因为在划分组的时候很容易将两个相同的数据交换位置导致不稳定。