希尔排序又叫缩小增量排序,是1959年 Shell 发明的第一个突破O(n2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。
这种排序是把一定间隔的数当做一个数组,在每个分好的数组里进行插入排序,再缩小间隔重复步骤,直到间隔为1,直接插入排序所有数。
这样开始每组数据量都比较小,可以发挥插入的优势,排序速度比较快,在缩小间隔后,虽然个数增加,但是因为之前已经粗略排了序,有序性比较高,也能发挥插入的优势,提高效率。
那么这个间隔(增量)取多少比较好?
增量的取法有各种方案。最初 Shell 提出取 increment=n/2 向下取整,increment=increment/2 向下取整,直到 increment=1。但由于直到最后一步,在奇数位置的元素才会与偶数位置的元素进行比较,这样使用这个序列的效率会很低。后来 Knuth 提出取 increment=n/3 向下取整 +1 ,还有人提出都取奇数为好,也有人提出 increment 互质为好。应用不同的序列会使希尔排序算法的性能有很大的差异。 这里使用 n/3 向下取整 +1 的方法。
举个栗子来理解:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
12 | 8 | 5 | 7 | 14 | 1 | 6 | 2 |
第一趟,取增量 increment = n/3 + 1 = 3,将数列划分为以 3 为间隔的数列(这里用同种颜色表示同一序列)
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
12 | 8 | 5 | 7 | 14 | 1 | 6 | 2 |
在每个序列中进行插入排序
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
6 | 8 | 5 | 7 | 14 | 1 | 12 | 2 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
6 | 2 | 5 | 7 | 8 | 1 | 12 | 14 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
6 | 2 | 1 | 7 | 8 | 5 | 12 | 14 |
第一趟结束后的排序
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
6 | 2 | 1 | 7 | 8 | 5 | 12 | 14 |
再开始第二趟之旅,这次我们取增量为 increment = increment/3 + 1 = 2(都是向下取整),为了看得清咱一组一组来
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
6 | 2 | 1 | 7 | 8 | 5 | 12 | 14 |
排序后结果
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
1 | 2 | 6 | 7 | 8 | 5 | 12 | 14 |
同样的,另一组排序
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
1 | 2 | 6 | 5 | 8 | 7 | 12 | 14 |
上面那个就是第二趟排序结果啦,最后第三趟排序,也就是增量为1(increment = increment/3 + 1 = 1),直接插入排序就可得到最终结果
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
1 | 2 | 5 | 6 | 7 | 8 | 12 | 14 |
主要代码
其实就是直接插入排序的外层多了个间隔 increment 的循环
void shell_sort(int a[], int n)
{
int increment = n;
do
{
increment = increment/3 + 1;
for(int i=increment;i<n;i++)
{
int t = a[i];
int flag = i-increment;
while(flag>=0 && a[flag]>t)
{
a[flag+increment] = a[flag];
flag -= increment;
}
a[flag+increment] = t;
}
}while(increment>1);
}