希尔排序介绍
希尔排序(Shell Sort)是插入排序的一种,它是针对直接插入排序算法的改进。该方法又称缩小增量排序,因DL.Shell于1959年提出而得名。
希尔排序实质上是一种分组插入方法。它的基本思想是:对于n个待排序的数列,取一个小于n的整数gap(gap被称为步长)将待排序元素分成若干个组子序列,所有距离为gap的倍数的记录放在同一个组中;然后,对各组内的元素进行直接插入排序。 这一趟排序完成之后,每一个组的元素都是有序的。然后减小gap的值,并重复执行上述的分组和排序。重复这样的操作,当gap=1时,整个数列就是有序的。
希尔排序说明
假设待排序文件有10个记录,其关键字分别是:
49,38,65,97,76,13,27,49,55,04。
增量序列的取值依次为:
5,3,1
希尔排序gap步长的选取
增量序列的选择
Shell排序的执行时间依赖于增量序列。
好的增量序列的共同特征:
① 最后一个增量必须为1;
② 应该尽量避免序列中的值(尤其是相邻的值)互为倍数的情况。
有人通过大量的实验,给出了目前较好的结果:当n较大时,比较和移动的次数约在到
之间。
增量的选择影响到排序的时间!
希尔排序的实现
希尔排序的实现(算法:C语言实现 P172)
void shellSort(Item a[], int l, int r)
{
int i, h, k;
for(h=1; h<=(r-l)/9; h=3*h+1); //根据待排序的元素数,决定希尔增量的最大值
for(; h>0; h/=3) //外层循环为增量递减
{
for(i=l+h; i<=r; i++)
{
int j=i;
Item v = a[i];
while(j>=l+h && less(v, a[j-h]))
{
a[j]=a[j-h];
j -= h;
}
a[j] = v;
}
}
}
Ps:这里采用的希尔增量是从1开始,通过乘3加1得到下一个步长。但在程序实现中,是先通过l和r来确定增量的最大值。
网上通用的希尔增量是2的倍数,如1 2 4 8 16 32 64……,但是书上说,这是不好的步长序列,直到最后一遍,步长为1时,在奇数位置的元素才会与偶数位置上的元素进行比较。
只需要修改上面程序中产生步长的外部循环。
希尔排序的完成实现代码
#include <stdio.h>
#include <stdlib.h>
typedef int Item;
#define key(A) (A)
#define less(A, B) (key(A)<key(B))
#define exch(A, B) {Item t = A; A = B; B = t;}
#define compexch(A, B) if(less(B, A)) exch(A, B) //最后A<B
/*
希尔排序:i下标元素前面是已经排过序的
*/
void selection(Item a[], int l, int r)
{
int i, j;
for(i=l; i<r; i++)
{
int min = i; //用于存放选择时候扫描的最小值
for(j=i+1; j<=r; j++)
{
if(less(a[j], a[min])) min=j;
}
exch(a[i], a[min]);
}
}
void shellSort(Item a[], int l, int r)
{
int i, h, k;
for(h=1; h<=(r-l)/9; h=3*h+1); //根据待排序的元素数,决定希尔增量的最大值
for(; h>0; h/=3) //外层循环为增量递减
{
for(i=l+h; i<=r; i++)
{
int j=i;
Item v = a[i];
while(j>=l+h && less(v, a[j-h]))
{
a[j]=a[j-h];
j -= h;
}
a[j] = v;
}
printf("\n增量为%d,排序后\n", h);
for(k=l; k<r; k++)
{
printf("%d ", a[k]);
}
}
}
int main(void)
{
int i, N, sw;
N = 50;
sw = 1;
int *a = (int *)malloc(N*sizeof(int)); //为数据a分配相应的存储空间
if (sw)
{
for(i=0; i<N; i++)
{
a[i] = 1000 * (1.0 * rand()/RAND_MAX);
}
}
else
{
for (i = 0; i < N; i++)
{
scanf("%d", &a[i]);
}
}
puts("排序前");
for(i=0; i<N; i++)
{
printf("%d ", a[i]);
}
shellSort(a, 0, N-1);
puts("\n排序后");
for(i=0; i<N; i++)
{
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
希尔排序的时间复杂度和稳定性分析
希尔排序时间复杂度
希尔排序的时间复杂度与增量(即,步长gap)的选取有关。例如,当增量为1时,希尔排序退化成了直接插入排序,此时的时间复杂度为O(N²),而Hibbard增量的希尔排序的时间复杂度为O(N3/2)。希尔排序稳定性
希尔排序是不稳定的算法,它满足稳定算法的定义。对于相同的两个数,可能由于分在不同的组中而导致它们的顺序发生变化。算法稳定性 -- 假设在数列中存在a[i]=a[j],若在排序之前,a[i]在a[j]前面;并且排序之后,a[i]仍然在a[j]前面。则这个排序算法是稳定的!
希尔排序的分析
Shell排序的时间性能优于直接插入排序
希尔排序的时间性能优于直接插入排序的原因:
①当文件初态基本有序时直接插入排序所需的比较和移动次数均较少。
②当n值较小时,n和的差别也较小,即直接插入排序的最好时间复杂度O(n)和最坏时间复杂度0(
)差别不大。
③在希尔排序开始时增量较大,分组较多,每组的记录数目少,故各组内直接插入较快,后来增量逐渐缩小,分组数逐渐减少,而各组的记录数目逐渐增多,但由于已经按
作为距离排过序,使文件较接近于有序状态,所以新的一趟排序过程也较快。
因此,希尔排序在效率上较直接插人排序有较大的改进。