排序之希尔排序
希尔排序属于插入排序的一种方式,不同于直接插入排序,希尔排序实际上是一种分组插入排序。先来说一下它的基本思想:先取定一个小于n的整数d1作为第一个增量,把表的全部元素分成d1个组,所有相互之间距离为d1的倍数的元素放在同一个组中,在各组内进行直接插入排序;然后,取第二个增量d2(d2<d1),重复上述的分组和排序过程,直至所取的增量d(t)=1(d(t)<d(t-1)<……d2<d1),即所有元素放在同一组中进行直接插入排序。
希尔排序在每趟并不产生有序区,在最后一趟排序结束前,所有元素并不一定归位。但是在每趟排序完成后,数据越来越接近有序。因为具体实现与前面总结的直接插入排序有相似之处,这里先多说一下关于希尔排序时间复杂度的问题再最后给出代码。
希尔排序的性能分析是一个复杂的问题,因为它的时间复杂度是所取“增量”的函数,而到目前为止增量的选取无一定论,因而它的时间复杂度难以分析,一般认为其平均时间复杂度为O(n^1.3)。希尔排序的速度通常要比直接插入排序快,可以这样理解:在希尔排序开始时增量d1较大,分组较多,每组的元素数目少,故各组内直接插入排序较快,后来增量d(i)逐渐缩小,分组数逐渐减少。而各组内的元素数目逐渐增多,但由于已经按d(i-1)作为增量排过序,使表较接近有序状态,所以新的一趟排序过程也较快。因此,希尔排序在效率上较直接插入排序有较大的改进。
希尔排序算法中使用了多重循环,因而我们要对每一层循环的目的有清晰的认识,在算法中使用了较多的辅助变量,但与问题规模无关,属于就地排序。下面给出一种可行的实现方式,增量初始设置为n/2,每进行完一次对所有组的组内直接插入排序,增量变为n/2,直到增量为1(即对所有的数字进行一次直接插入排序)为止,此时排序完成。代码如下,关键代码的解释写在注释里,便于查看:
//包含了直接插入排序
void Sort_shell(int *a, int n)
{
inti,j,gap;
for(gap=n/2;gap>0; gap/=2)
//gap代表了选取的增量,即每次分几组,也即组内元素的距离最小值
//在每组的组内再进行直接插入排序
{
for(i=0;i<gap; i++)//gap组数字全部进行下面的直接插入排序
{
//每组的组内数字进行直接插入排序
for(j=i+gap;j<n; j+=gap)
if(a[j]<a[j-gap])
{
inttemp = a[j];
intk = j-gap;
//边比较边进行移动覆盖,直到把该元素插入到合适的位置
//然后再进行同一组内的下一个元素的直接插入排序,直到该组排序结束
while(k>=0&& a[k]>temp)
{
a[k+gap]= a[k];
k-=gap;
}
a[k+gap]=temp;
}
}
}
}
还是举例说明一下:
初始数组: 元素下标 0 1 2 3 4 5 6 7
元素值 12 8 4 7 3 16 2 5
第一次分组插入排序:
gap=4 共四组(12,3) (8,16) (4,2) (7,5)
排序结束元素顺序为:3 8 2 5 12 16 4 7
第二次分组插入排序:
gap=2 共两组 (3 2 12 4) (8 5 16 7)
排序结束元素顺序为:2 5 3 7 4 8 12 16
第三次分组插入排序:
gap=1 共一组(2 5 3 7 4 8 12 16)
排序结束元素顺序为:2 3 4 5 7 8 12 16
至此,整个排序过程结束
类似于直接插入排序,再给出一种简化的代码实现方式:
void Sort_Shell(int *a ,int n)
{
int i,j,gap;
for(gap=n/2; gap>0; gap/=2)
for(i=gap; i<n; i++)
//不同组的数字交替的进行插入排序,比较难想,建议在纸上把程序走一遍
for(j=i-gap; j>=0&& a[j]>a[j+gap]; j-=gap)
swap(a[j],a[j+gap];)
}