希尔排序
希尔排序算法是突破这个时间负责度(O())的第一批算法之一。之前的直接插入排序,应该说,它的效率在某些时候是很高的,比如,我们的记录本身就是基本有序的,我们只需要少量的插入操作,就可以完成整个记录集的排序工作,此时直接插入很高效。还有就是记录数比较少时,直接插入的优势也比较明显。问题在于,两个条件本身就过于苛刻。现实中记录少或者基本有序都属于特殊情况。
如何让待排序的记录个数较少呢?很容易想到就是将原来的大量记录数的记录进行分组。分割成若干个子序列,此时每个子序列待排序的记录个数就比较少了。然后在这些子序列内分别进行直接插入操作,当整个序列基本有序时,注意只是基本有序时,再对全体记录进行一次直接插入排序。
所谓的基本有序,就是小的关键字基本在前面,大的基本在后面,不大不小的基本在中间,像{2,1,3,6,4,7,5,8,9}这样可以称为基本有序了,但像{1,5,9,3,7,8,2,4,6}这样的9在第三位,2在倒数第三位就谈不上基本有序。
我们分割待排序记录的目的是减少待排序记录的个数,并且使整个序列向基本有序发展。因此,我们需要采取跳跃分割的策略:将相距某个"增量"的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序**。
希尔排序算法
运行示例:
/* 对顺序表L作希尔排序*/
#include <stdio.h>
#define MAXSIZE 10
typedef struct
{
int r[MAXSIZE];
int length;
}SqList;
void swap(SqList *L, int i, int j)
{
int temp = L->r[i];
L->r[i] = L->r[j];
L->r[j] = temp;
}
void ShellSort(SqList *L)
{
int i, j;
int increment = L->length;
do
{
increment = increment/3 + 1; /*增量序列*/
for(i = increment + 1; i <= L->length; i++)
{
if(L->r[i] < L->r[i-increment])
{
/*需将L->r[i]插入有序增量子表*/
L->r[0] = L->r[i]; /*暂存在L->r[0]*/
for(j = i-increment; j>0&&L->r[0]<L->r[j]; j -= increment)
{
L->r[j+increment] = L->r[j]; //记录后移,查找插入位置
}
L->r[j+increment] = L->r[0]; //插入
}
}
} while (increment > 1);
}
int main()
{
SqList H;
int n;
scanf("%d",&n);
H.length = n-1; //实际长度为n-1
for(int i = 0; i < n; i++)
{
scanf("%d",&H.r[i]);
}
ShellSort(&H);
for(int i = 1; i < n; i++)
{
printf("%d ",H.r[i]);
}
}
/*
------------------
程序运行示例:
10
0 9 1 5 8 3 7 4 6 2
1 2 3 4 5 6 7 8 9 %
------------------
*/
希尔排序复杂度分析
希尔排序的关键并不是随便分组后各自排序,而是将相隔某个"增量"的记录组成一个子序列,实现跳跃式的移动,使得排序的效率提高。
这里的增量的选取非常的关键,代码中increment=increment/3+1;的方式选取增量的,可究竟应该选取什么样的增量才是最好,目前还是一个数学难题,迄今为止没有找到一个最好的增量排序。不过大量的研究表明,当增量序列为dlta[k] = 时,可以获得不错的效果,其时间复杂度为O()要好于直接排序的O()。其中需要注意的是,增量序列的最后一个增量值必须等于1才行。另外由于记录是跳跃式的移动,希尔排序并不是稳定的排序算法。