一、排序方法之希尔排序(数据结构)

希尔排序的理解

作为插入排序的升级版,希尔排序的优化体现在何处呢?具体需要如何实现呢?

一、写在前面

【Definition】An inversion in an array of numbers is any ordered pair ( i, j ) having the property that i < j but A[i] > A[j].

逆序:是指序列中大小关系不正确的数目。

举个栗子:

34, 8, 64, 51, 32, 21 这样一个数字序列具有9个逆序。
分别是(34, 8) (34, 32) (34, 21) (64, 51) (64, 32) (64, 21) (51, 32) (51, 21) (32, 21)。

从逆序角度理解插入排序

我们的插入排序在每一步时都要像打扑克一样,将未排好序的牌中取一张,插入到已排序的牌堆中,在插入的过程,我们可以理解成为逆序减少的过程,因为每次都是相邻元素的交换,很容易理解交换一次最多消除一个逆序。
而N个元素逆序的最大值是 N ( N − 1 ) 2 \frac{N(N-1)}{2} 2N(N1),不难理解插入排序的时间复杂度是 O ( N 2 ) O(N^2) O(N2)

二、希尔排序的思想

自然语言(文字)描述

希尔排序为了使得交换减少,采取的策略是进行较远元素的比较,这样一次交换即可实现多个逆序的减少,如{5,4,2,6},如果采取插入排序,需要交换三次;而希尔排序如果进行5与2的交换,则一次可以减少三个逆序,成功使序列有序。

而这样的分组一般是有规律的,采取不同的分组策略会有不同的时间复杂度。

最常用到的递增序列:
h t = ⌊ N / 2 ⌋ , h k + 1 = ⌊ h k / 2 ⌋ h_t= \lfloor N/2 \rfloor,h_k+1 = \lfloor h_k/2\rfloor ht=N/2,hk+1=hk/2

伪代码描述

void Shellsort( ElementType A[ ], int N ) 
{ 
      int  i, j, Increment; 
      ElementType  Tmp; 
      for ( Increment = N / 2; Increment > 0; Increment /= 2 )  
	/*h sequence :希尔序*/
	for ( i = Increment; i < N; i++ ) { /* insertion sort */
	      Tmp = A[ i ]; 
	      for ( j = i; j >= Increment; j - = Increment ) 
		if( Tmp < A[ j - Increment ] ) 
		      A[ j ] = A[ j - Increment ]; 
		else 
		      break; 
		A[ j ] = Tmp; 
	} /* end for-I and for-Increment loops */
}

二话不说,先上栗子:

在这里插入图片描述
上图是以{5,3,1}作为希尔递增序列(increment sequence)完成的希尔排序:

  1. 首先是间隔为5进行排序,我们可以分成五组,在图中的第一行,用蓝、橙、粉红、绿和紫罗兰色进行了分组,组内分别进行插入排序,即81,35,41先进行排序,结果为35,41,81分别填入原来这三个数所在的三个空,其它四个组以此类推。
  2. 接着是间隔为3进行排序,我们可以分成三组,在图中的第二行,如{35,28,75,58,95}就是一组,排序结果应为{28,35,58,75,95},重新填入所在位置,其它两个组以此类推。
  3. 进行完这两步,我们发现序列基本有序,这个时候再进行插入排序就可以减少比较多的交换了。

三、希尔排序的C语言实现

#include <stdio.h>
#include <stdlib.h>

#define SIZE 20

void Shell_sort(int a[],int size);

void prt(int a[],int len){
    for(int i=0;i<len;i++){
        printf("%4d",a[i]);
    }
    printf("\n");
}

int cnt = 0;

int main(){
    int a[SIZE];
    srand(1);
    for(int i=0;i<SIZE;i++){
        a[i] = rand() % SIZE; 
    }
    prt(a,SIZE);
    printf("-------------------------------------------------\n");

    Shell_sort(a,SIZE);
    printf("The number of swap :%d\n",cnt);
    int suc =1;
    for(int i=0;i<SIZE-1;i++){
        if(a[i] > a[i+1]){
            suc =0;break;
        }
    }
    if(suc ){
        printf("ok\n");
    }else{
        printf("fail\n");

    }
}

void Shell_sort(int a[],int n){
    int Increment, i, j, temp;
    //希尔序:这里采取的递增序列是 n/2,n/4,...,1
    for (Increment = n/2; Increment >= 1; Increment = Increment /2) {
    	//插入第一个元素,直接跳过这一步,从插入第二个元素开始.因为这个小组的元素中隔了Increment 个,所以排的时候也要隔Increment 个
        for (i = Increment ; i < n; i++) {
            //一次进行各个小组的排序,并非是先进行一次排序(顺序是按照再数组中储存的位置)
            if(a[i] < a[i-Increment])
            {
                temp = a[i];
                for (j = i-Increment ; j >= 0 && temp < a[j];j -= Increment) {
                    a[j+Increment] = a[j];
                    cnt++;
                }
                a[j+Increment] = temp;
            }
        }
        prt(a,n);
    }
}

变更实验数据可以更改随机数种子,srand(num).

四、实验结果

希尔排序

在这里插入图片描述

插入排序

在这里插入图片描述

五、提高与改进

使用 Hibbard’s Increment Sequence:
h k = 2 k + 1 h_k=2^k+1 hk=2k+1 ---- 连续增量没有公共因子(互素)。
时间复杂度可以减少为 O ( N 5 / 4 ) O(N^{5/4}) O(N5/4).
还有更加复杂的序列,总之使用到了互素的性质进一步减少每次分组之间的重叠,增大分组的作用。

Sedgewick’s best sequence: 9 ∗ 4 i – 9 ∗ 2 i + 1 9*4^i – 9*2^i + 1 94i92i+1 or 4 i – 3 ∗ 2 i + 1 4^i – 3*2^i + 1 4i32i+1.

程序实现:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define SIZE 500000
#define MAXN 40

void Shell_sort(int a[],int size);

//打印输出数组
void prt(int a[],int len){
    for(int i=0;i<len;i++){
        printf("%5d ",a[i]);
        if(i%15 == 14){
            printf("\n");
        }
    }
    printf("\n");
}

//计数交换次数
int cnt = 0;
int shell_series[MAXN];
int index = 0;

int main(){
    int a[SIZE];
    //生成随机数组
    srand(1);
    for(int i=0;i<SIZE;i++){
        a[i] = rand() % SIZE; 
    }
    // prt(a,SIZE);
    printf("-------------------------------------------------\n");

    //希尔排序
    //希尔序:Sedgewick's best sequence
    int ix,iy;
    for(index = 0,ix = 0,iy = 2;;ix++,iy++){
        int num1 = 9*pow(4,ix)-9*pow(2,ix)+1;
        int num2 = pow(4,iy)-3*pow(2,iy)+1;
        if(num2 < SIZE && num1 < num2){        //当num1 < num2 < n时,依次读入num1以及num2
            shell_series[index++] = num1;
            shell_series[index++] = num2;
        }
        else if(num1 < SIZE && num1 > num2){   //当num2 < num1 < n时,依次读入num2以及num1
            shell_series[index++] = num2;
            shell_series[index++] = num1;
        }
        else if(num2 < SIZE){                  //当num2 < n < num1时,读入num2
            shell_series[index++] = num2;
        }
        else if(num1 < SIZE){                  //当num1 < n < num2时,读入num1
            shell_series[index++] = num1;
        }
        else if(num1 > SIZE && num2 > SIZE)       //当n < num1 && n < num2,结束寻找
            break;
    }
    Shell_sort(a,SIZE);
    printf("The number of swap :%d\n",cnt);
    //判断结果正确性
    int suc =1;
    for(int i=0;i<SIZE-1;i++){
        if(a[i] > a[i+1]){
            suc =0;break;
        }
    }
    if(suc ){
        printf("ok\n");
    }else{
        printf("fail\n");

    }
}

void Shell_sort(int a[],int n){
    prt(shell_series,index);

    int Increment, i, j, temp;
    index --;
    for (Increment = shell_series[index]; index >= 0; Increment = shell_series[--index]) {
    	//插入第一个元素,直接跳过这一步,从插入第二个元素开始.因为这个小组的元素中隔了Increment 个,所以排的时候也要隔Increment 个
        for (i = Increment ; i < n; i++) {
            //一次进行各个小组的排序,并非是先进行一次排序(顺序是按照在数组中储存的位置)
            if(a[i] < a[i-Increment])   //当前小组进行插入排序
            {
                temp = a[i];            //找到正确的位置
                for (j = i-Increment ; j >= 0 && temp < a[j]; j -= Increment) {
                    a[j+Increment] = a[j];
                    cnt++;
                }
                //插入当前元素
                a[j+Increment] = temp;
            }
        }
        // prt(a,n);
    }
}

以500000为数据量,进行测试,上面为采用Sedgewick’s best sequence的结果,优于下面的一般希尔排序(简单二分)。
在这里插入图片描述

在这里插入图片描述

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值