排序归纳之插入排序

插入排序分为以下几类:

1:直接插入排序

2:折半插入排序

3: 2-路插入排序

4:表插入排序

5:希尔排序

其中,2-4是减少比较和移动次数来提高性能。


下面一个一个介绍原理,算法实现以及性能分析:

一:直接插入排序

1:原理:

本算法是从1-n遍历进行排序,当排序第i个数据的时候,前面i-1个数据已经有序;然后将第i个数据从i-1个数据开始,依次往前比较,插入到合适的位置。

2:算法实现:

void insertion_sort1(int arr[],int n)
{
    int i,j;
    int key;

    for(i=1;i<n;i++){
        key = arr[i];
        for(j=i-1;j>=0;arr[j+1]=arr[j],j--){
            if(arr[j]<=key){
                arr[j+1]=key;
                break;
            }
        }
    }
}

或者内层循环利用while更简洁一些:

void insertion_sort2(int arr[],int n)
{
    int i,j;
    int key;

    for(i=1;i<n;i++){
        key = arr[i];
        j=i-1;
        while(j>=0&&arr[j]>key){
            arr[j+1]=arr[j];
            j--;
        }
        arr[j+1]=key;
    }
}

因此,不能一味的用for循环,尝试while看看。

3:分析:

(1)该算法实现的是原址排序。

(2)时间复杂度:

最好情况(顺序有序):O(n)——比较次数(n-1),移动次数0。

最坏情况(逆序有序):O(n^2)——比较次数(n+2)(n+1)/2,移动次数(n+4)(n+1)/2

(3)排序是稳定的。


二:折半插入排序

1:原理

当要插入第i个数据的时候,前面i-1个数据已经有序,那么我们可以利用折半查找的方法,找到要插入的位置。然后从该位置到i-1依次移动数据。

2:实现:

void binary_insert_sort(int arr[],int n)
{
    int i,j;
    int key;
    int high,low,mid;

    for(i=1;i<n;i++){
        key=arr[i];
        high=i-1;
        low=0;
        while(low<=high){
            mid=(low+high)/2;
            if(key<arr[mid])
                high=mid-1;
            else
                low=mid+1;
        }
        for(j=i-1;j>high;j--)
            arr[j+1]=arr[j];
        arr[j+1]=key;
    }
}

3:分析

(1)该算法也是原址排序,在直接插入排序的基础上减少比较次数

(2)时间复杂度:最好情况O(n);最坏情况O(n^2)

(3)注意:1、最后for循环时必须要插入high+1的位置,通过比较画图仔细分析得出。2、当key<arr[mid]表示的时候,是稳定排序;当key<=arr[mid]时,是不稳定排序。


三:2-路插入排序

1:原理

2-路插入排序在折半插入排序的基础上改进,目的是减少排序过程中移动的次数,但为此要n个记录的辅助空间。
具体思路:另设一个与含有n个数据的待排数据序列同类型的数组d,把待排序列的第一个元素(根据自己的数据结构来确定)赋值给d[0],并将d[0]看做是在排好序的序列的中间位置,然后将剩下的n-1个序列一次插入到d[0]之前或之后的有序数据序列(将数组d看做循环向量)中,这时插入利用直接插入。
实现算法的时候,要设两个指针first和final。

2:实现

void path2_insert_sort(int arr[],int n)
{
    int final = 0,first = 0;
    int *buf = (int *)malloc(n*sizeof(int));

    for(int k=0;k<n;k++)
        buf[k]=0;

    buf[0] = arr[0];
    for(int i=1;i<n;i++){
        if(arr[i]<buf[0]){      //插入左边
            if(arr[i]<buf[first]){
                first = (first-1+n)%n;      //不能单纯的用(first-1)% n,当first-1为负数时得不到想要的结果
                buf[first]=arr[i];
            }
            else{  //插入中间
                int j;
                for(j=first;j<n && arr[i]>=buf[j];j++)
                    buf[j-1]=buf[j];
                buf[j-1]=arr[i];
                first--;
            }
        }else{                  //插入右边
            int j;
            for(j=final;j>=0 && arr[i]<buf[j];j--)      //将arr[i]<buf[j];放入循环中比较会简化代码!
                buf[j+1]=buf[j];
            buf[j+1]=arr[i];
            final++;
        }
    }
#ifdef SIGN
    int i,k;
    for(i=0,k=first;k!=final;k=(k+1)%n,i++){
        arr[i]=buf[k];
    }
    arr[i]=buf[k];          //必须另外加上去,否则无法复制buf[final]
#else
    printf("else\n");
    int i;
    for(i=0;i<n;i++){             //循环中又一次利用了取余,比上面的代入优化代码!
        arr[i]=buf[(first++)%n];
    }
#endif
    printf("\n****\n");
}


3:分析

此算法相对折半插入算法来说,能减少移动记录的次数,但是不能绝对避免移动记录(除非是已排好序的数据序列),移动记录的次数约为N^2/8。根据由折半插入排序改进而来的2路插入排序,我不由自主的就想到了能不能来个2-路快速插入排序,即结合折半插入排序、快速排序优点的排序。
技巧:

   (1)函数中要定义一个跟传进来的数组一样大小的数组,要么全局定义一个MAX常量,要么用动态分配的方法malloc来分配内存。

   (2)如果对一个数组初始化,可以用for循环,更方便的是利用memset()函数来实现,详见:
            http://blog.csdn.net/a45872055555/article/details/27187357
   (3)时间复杂度依然是O(n^2)

四:表插入排序(略,以后再补)

1:原理


2:实现


3:分析





五:希尔排序(又称“缩小增量排序”)

此处参考大牛帖子:http://blog.csdn.net/morewindows/article/details/6668714

1:原理

由直接插入排序的两个优点进化而来:当待排序列基本有序时,时间复杂度降为O(n);直接插入排序算法简单,在n很小时效率高。

基本思想:先将整个待排序列分割成若干子序列分别进行插入排序,待整个记录基本有序时,对整体进行一次插入排序。

特点:子序列的构成不是简单的“逐段分割”,而是将相隔某个“增量”的记录组成一个子序列。一般选择:for(gap=n/2;gap>0;gap=gap/2);

以n=10的一个数组49, 38, 65, 97, 26, 13, 27, 49, 55, 4为例

第一次 gap = 10 / 2 = 5

49   38   65   97   26   13   27   49   55   4

1A                                        1B

        2A                                         2B

                 3A                                         3B

                         4A                                          4B

                                  5A                                         5B

1A,1B,2A,2B等为分组标记,数字相同的表示在同一组,大写字母表示是该组的第几个元素, 每次对同一组的数据进行直接插入排序。即分成了五组(49, 13) (38, 27) (65, 49)  (97, 55)  (26, 4)这样每组排序后就变成了(13, 49)  (27, 38)  (49, 65)  (55, 97)  (4, 26),下同。

排序后

13   27   49   55   4    49   38   65   97   26

第二次 gap = 5 / 2 = 2

13   27   49   55   4    49   38   65   97   26

1A             1B             1C              1D            1E

        2A               2B             2C             2D              2E

排序后:

4   26   13   27   38    49   49   55   97   65

第三次 gap = 2 / 2 = 1

4   26   13   27   38    49   49   55   97   65

1A   1B     1C    1D    1E      1F     1G    1H     1I     1J

排序后:

4   13   26   27   38    49   49   55   65   97

第四次 gap = 1 / 2 = 0 结束!


2:实现

方法一:下面给出严格按照定义来写的希尔排序

void shellsort1(int arr[],int n)
{
    int i,j,gap;

    for(gap=n/2;gap>0;gap=gap/2){  //步长逐级降低,直到gap等0时,对整个数组排序
        for(i=0;i<gap;i++){         //固定步长时,对排列进行遍历,一共需要gap次排序。
            for(j=i+gap;j<n;j+=gap){
                if(arr[j]<arr[j-gap]){
                    int temp = arr[j];
                    int k = j-gap;
                    while(k>=0 && arr[k]>temp){     //利用条件相与,简化代码结构!
                        arr[k+gap] = arr[k];
                        k = k-gap;
                    }
                    arr[k+gap] = temp;
                }
            }
        }
    }
}

上面的shellsort1代码虽然对直观的理解希尔排序有帮助,但代码量太大了,不够简洁清晰。因此进行下改进和优化,以第二次排序为例,原来是每次从1A到1E,从2A到2E,可以改成从1B开始,先和1A比较,然后取2B与2A比较,再取1C与前面自己组内的数据比较…….。这种每次从数组第gap个元素开始,每个元素与自己组内的数据进行直接插入排序显然也是正确的。(省略了一层for循环,代码简洁)

void shell_sort2(int arr[],int n)
{
    int j,gap;
    for(gap=n/2;gap>0;gap/=2){
        for(j=gap;j<n;j++){
            if(arr[j]<arr[j-gap]){
                int temp = arr[j];
                int k = j-gap;
                while(k>=0 && arr[k]>temp){
                    arr[k+gap]=arr[k];
                    k=k-gap;
                }
                arr[k+gap]=temp;
            }
        }

    }
}

还能够更简化代码:当arr[j]>arr[j-gap];就交换两个数值,直到 arr[j]<=arr[j-gap];为止。但这种方法虽然代码简洁,却增加了数值的移动。

void shellsort3(int a[], int n)  
{  
    int i, j, gap;  
  
    for (gap = n / 2; gap > 0; gap /= 2)  
        for (i = gap; i < n; i++)  
            for (j = i - gap; j >= 0 && a[j] > a[j + gap]; j -= gap)  
                Swap(a[j], a[j + gap]);  
}

附注:上面希尔排序的步长选择都是从n/2开始,每次再减半,直到最后为1。其实也可以有另外的更高效的步长选择,如果读者有兴趣了解,请参阅维基百科上对希尔排序步长的说明:

http://zh.wikipedia.org/wiki/%E5%B8%8C%E5%B0%94%E6%8E%92%E5%BA%8F

3:分析

(1).增量序列的选择
     Shell排序的执行时间依赖于增量序列。
     好的增量序列的共同特征:
  ① 最后一个增量必须为1;
  ② 应该尽量避免序列中的值(尤其是相邻的值)互为倍数的情况。
     有人通过大量的实验,给出了目前较好的结果:当n较大时,比较和移动的次数约在nl.25到1.6n1.25之间。


(2).Shell排序的时间性能优于直接插入排序
     希尔排序的时间性能优于直接插入排序的原因:
  ①当文件初态基本有序时直接插入排序所需的比较和移动次数均较少。
  ②当n值较小时,n和n2的差别也较小,即直接插入排序的最好时间复杂度O(n)和最坏时间复杂度0(n2)差别不大。
  ③在希尔排序开始时增量较大,分组较多,每组的记录数目少,故各组内直接插入较快,后来增量di逐渐缩小,分组数逐渐减少,而各组的记录数目逐渐增多,但由于已经按di-1作为距离排过序,使文件较接近于有序状态,所以新的一趟排序过程也较快。
     因此,希尔排序在效率上较直接插人排序有较大的改进。

(3).稳定性
     希尔排序是不稳定的。参见上述实例,该例中两个相同关键字49在排序前后的相对次序发生了变化。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值