昨天介绍了插入排序,今天介绍下简单插入排序的优化--------希尔排序。
对于插入排序,我们其实不难发现如果数据本来就基本有序,那么数据插入移动的次数就会非常少。要注意这里这里说的基本有序不等于局部有序,例如下面的数列可以当做基本有序:{1,2,8,4,5,6,7,3},是整体而言,而局部有序的例子如下:{6,7,8,9,1,2,3,4,5},这里只是数据的部分有序,对于整体而言,其实移动次数不会减少太多。其实这也是非常不好量化的概念,总之,大家要知道的是,简单的插入排序是有办法优化的,而优化的方法就如下:
将整个数列分成子序列,但要注意这里说的子序列并不是简单的连续逐段分割,而是离散化,具体的方法就是把相隔相同增量的数据记录变为一组,形成子序列。这样分割的方法就能保证每组排序好后,对于整体而言就是基本有序,而如果用逐段分割那么即便每组都有序了,那也是部分有序,大家可以参考刚才提到的的例子:{6,7,8,9,1,2,3,4,5},而对于增量的取法,可以使用希尔最早提出的方法d=n/2,之后每次增量减半向下取整,最后只要保证d=1即可。具体的例子如下:
初始序列如下:
一共十个元素,取10=10/2=5,则分组情况如下(相同颜色的为一组):
很简单地能看到,分成了五组{59,14},{20,23},{17,83},{36,13},{98,28},之后对每一组利用简单的插入排序,只是不同的是间隔不是简单排序中的1,而是这里的d=5
在d=5时每组进行排序后,并以d=d/2=5/2=2 为新的间隔进行分组后,结果如下:
大家要注意,每组排序的结果不影响其他组的排序,比如{59,17}排序后变成{17,59},但是这里我们看到23<59仍然是在59后面的。
在d=2后,这时就只有两组{14,17,28,23,36},{20,13,59,83,98},这时再每组进行简单插入排序,和d=5时一样,不多BB.
以下为d=2排序后的结果和以新的d=d/2=2/2=1进行分组:
在d=1时,整个数列变为一组,下面进行的动作就是为简单插入的步骤,也就是说我们之前做的工作都是为了使数列更加基本有序,以便在进行d=1的简单插入排序时能大幅度的减少移动次数,以下是d=1排序后的结果,也就是最终的结果 :
相信到这里,大家可能会有困惑,在进行了大量的d不同排序后,最后还是要进行d=1的简单插入排序,明显比简单插入排序要更复杂啊。我要告诉大家的是,复杂是复杂,只不过这里的复杂是思路复杂,在对于计算机来说,其运行的效率其实是提高的。大家可以想象下,在一开始d很大时候,每组的数据很少,即便要把数据插入到每组第一个位置,移动的次数也不会很多,并且在多次的d变化后,在进行d=1的排序后,其实数列已经基本有序了,这时再进行简单的插入排序,移动的次数是大大减少了,效率也就高了。但是要注意的是,虽然移动的次数减少了,但是比较的次数确实是增加了,但是其增加的时间远小于移动次数减少带来的减少的时间,所以总体上,希尔排序效率是提高的了,事实证明,简单排序的时间复杂度是而希尔排序的时间复杂度在
到
。
以下是希尔排序的代码:
#include<stdio.h>
void Shell_Sort(int A[],int N)
{
for(int d=N/2;d>=1;d=d/2)
{
for(int i=d;i<N;i++) //从每组的第二个元素开始插入,和简单插入排序一样,默认第一个元素是有序的
{
int v=A[i]; //下面按照简单插入排序的方法,将要插入的数据提取出来
int j=i-d; // 这里与简单插入排序不同
while(j>=0 && A[j]>v)
{
A[j+d]=A[j];
j-=d;
}
A[j+d]=v;
}
}
}
int main()
{
int N;
int A[100];
scanf("%d",&N);
for(int i=0;i<N;i++)
scanf("%d",&A[i]);
Shell_Sort(A,N);
printf("希尔排序\n");
for(int i=0;i<N;i++)
printf("%d ",A[i]);
return 0;
}
PS:晚安~
O(