一些说明:在这里我假设的数组元素可以比较大小,且无重复元素,按照升序进行排序。
1、希尔排序的算法
希尔排序属于比较相距一定间隔的排序算法。
核心思想:比较的间隔逐渐减小直到最小间隔为1,只比较相邻元素的最后一趟排序为止。由于这个原因希尔排序有时也被叫做缩小增量排序(diminishing increment sort).
我的算法描述:
首先选择一个最小增量为1的增量序列。每次令当前增量为初始位置开始比较直到数组末尾;每次用一个中间变量保存当前位置元素,然后将中间变量与当前位置的前增量间隔的位置元素进行比较,若前面元素大则将其覆盖当前位置元素,然后将当前位置向前推进增量个单位,若前面元素小或当前位置小于当前增量则结束比较用中间变量覆盖当前位置元素(就是插入排序中为元素寻找到合适的位置进行插入)。然后,进行下一轮增量的比较直到最小增量1的最后一趟比较完成。
2、希尔排序的函数代码
希尔增量的希尔排序:
typedef int ElementType; //元素类型
const int N=1000; //数组最大长度
void Shellsort_Shell(ElementType num[],int length){
int i=0,j=0,increment=0;
ElementType temp=0;
for(increment=length/2;increment>0;increment/=2){ //希尔增量
for(i=increment;i<length;i++){
temp=num[i];
for(j=i;j>increment||j==increment;j-=increment){
if(temp<num[j-increment]) num[j]=num[j-increment];
else break;
}
num[j]=temp;
}
}
}
Hibard增量的希尔排序:
void Shellsort_Hibbard(ElementType num[],int length){
int i=length,j=0,increment=0,n=0; //n是length关于2的最大阶乘数
ElementType temp=0;
while(i>1){ //这个循环获得n
i/=2;
n++;
}
for(;n>0;n--){
increment=(int)pow(2,n)-1; //pow(x,y)计算x的y次方,返回结果
for(i=increment;i<length;i++){
temp=num[i];
for(j=i;j>increment||j==increment;j-=increment){
if(temp<num[j-increment]) num[j]=num[j-increment];
else break;
}
num[j]=temp;
}
}
}
3、希尔排序的分析
3.1 时间复杂度
使用希尔增量时希尔排序的下界是O(N²),使用Hibbard增量的希尔排序的下界是O(N的二分之三次方),证明太复杂了,不会。。。
3.2 运行时间实例分析
在这里我生成一个从1——N的随机数数组,N的选取为10000,1000000,1000000,太小了运行时间太短,注意要在main函数外面声明数组,main函数中声明的数组长度不能太大,因为main函数里面是局部变量放在堆栈段,局部变量太大会导致栈溢出。我在我的电脑上运行10次取平均值 。
生成随机数组的函数:
void Swap(ElementType* a,ElementType* b){
ElementType temp=*a;
*a=*b;
*b=temp;
}
void Randomize(ElementType a[],int length){
memset(a,0,sizeof(a)); //快速置0
srand((ElementType)time(NULL));
for(int i=0;i<length;i++) a[i]=i+1;
for(int i=length-1;i>0;i--) Swap(&a[i],&a[rand()%i]);
}
不同数组长度下的平均时间:
增量类型 | 10000 | 100000 | 1000000 | 10000000 |
---|---|---|---|---|
Shell增量 | 0.001 | 0.019 | 0.2253 | 2.408 |
Hibbard增量 | 0 | 0.0206 | 0.2344 | 2.484 |
在测试中两种增量的希尔排序大致时间相等,当然这有着较大的偶然性,因为我的样本小,实验次数少
4、 整体代码:
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<math.h>
typedef int ElementType; //元素类型
const int N=10000; //数组最大长度
void Randomize(ElementType [],int); //随机化一个数组,参数为数组地址和长度
void Swap(ElementType*,ElementType*); //交换函数
void Shellsort_Shell(ElementType [],int); //希尔增量的希尔排序
void Shellsort_Hibbard(ElementType [],int); //Hibbard增量的希尔排序
ElementType num[N]={0};
int main(void){
clock_t start=0,finish=0; //start,finish分别用来标记排序开始与结束的时间点
int i=0;
Randomize(num,N);
start=clock(); //排序开始的时间点
//Shellsort_Shell(num,N);
Shellsort_Hibbard(num,N);
finish=clock(); //排序结束时的时间点
for(;i<N;i++) printf("%d\n",num[i]);
printf("\n本次排序所用的时间为:\n%lf",(double)(finish-start)/CLOCKS_PER_SEC);
//CLOCKS_PRE_SEC 表示一秒钟内CPU运行的时钟周期数(时钟计时单元)
return 0;
}
void Shellsort_Shell(ElementType num[],int length){
int i=0,j=0,increment=0;
ElementType temp=0;
for(increment=length/2;increment>0;increment/=2){ //希尔增量
for(i=increment;i<length;i++){
temp=num[i];
for(j=i;j>increment||j==increment;j-=increment){
if(temp<num[j-increment]) num[j]=num[j-increment];
else break;
}
num[j]=temp;
}
}
}
void Shellsort_Hibbard(ElementType num[],int length){
int i=length,j=0,increment=0,n=0; //n是length关于2的最大阶乘数
ElementType temp=0;
while(i>1){ //这个循环获得n
i/=2;
n++;
}
for(;n>0;n--){
increment=(int)pow(2,n)-1; //pow(x,y)计算x的y次方,返回结果
for(i=increment;i<length;i++){
temp=num[i];
for(j=i;j>increment||j==increment;j-=increment){
if(temp<num[j-increment]) num[j]=num[j-increment];
else break;
}
num[j]=temp;
}
}
}
void Swap(ElementType* a,ElementType* b){
ElementType temp=*a;
*a=*b;
*b=temp;
}
void Randomize(ElementType num[],int length){
int i=0;
srand((ElementType)time(NULL));
for(;i<length;i++) num[i]=i+1;
for(i=length-1;i>0;i--) Swap(&num[i],&num[rand()%i]);
}
总结
平均下来看,希尔排序比插入排序还是要快不少。比较流行的增量序列就是希尔序列,但是这种序列的下届是平方级,不太好,可以改变增量序列来提高排序效率。