插入排序:二路插入

来源:互联网 发布:计算机三级软件 编辑:程序博客网 时间:2024/06/03 21:19

作者:苏叔叔

链接:http://blog.csdn.net/zhangxiangdavaid/article/details/27958143?utm_source=tuicool&utm_medium=referral

在上一篇博客中:插入排序:直接插入、交换插入、折半插入。提到了三种插入排序的具体实现。不过仍有改进的地方。例如序列 2 1 3,当把1往前插入时,由于1<2,则应当把1插入到2的前面。在上述三种插入排序方法的实现中,都是把1、2位置交换。于是,我们想有没有可能不进行交换,因为交换总是相当耗时的。但是1必须要排到2的前面,可2的前面没有位置了啊?嗯,初看是这样的。试想这是一个循环的数组呢?这就是二路插入最核心的想法。

思路:

  1. 构建一相同大小的循环数组b,把原数组的元素依次插入,最后按合适次序赋值回原数组。如何实现循环呢?有办法的。可参考约瑟夫问题的数组解法中是如何实现的。
  2. 把原数组的第一个值a[0]复制过去,b[0]=a[0],作为循环数组的第一个数。当然,也可选择其它的数作为第一个数。
  3. 若a[i]<b[first],则变化first:first=(first-1+n)%n,b[first]=a[i]
  4. 若a[i]>=b[last],则变化last:last++(注意这里没必要这样写:last=(last+1)%n),b[last]=a[i]
  5. 若b[first]<=a[i]<b[last],则选择适当的策略,插入下图中的一路位置。
  6. 这里的二路是什么意思?没有看到哪里解释过,我的理解是,看下图:

上图中,first指向已拍好序列的第一个,last指向已排好序列的最后一个。如果按从小到大排序,first指向最小,last指向最大的。如果某一个数据a,且b[first]<=a<b[last],则a应插入图中一路所示的位置,其它的应插入二路。也就是说,可以插入的位置总的分为两路-二路插入。
显然,一路位置的元素是有序的。那么在往一路插入时,可直接插入,也可二分插入,先看下直接插入时的代码:
代码一:
[cpp] view plain copy
  1. <span style="font-size:25px;">  
  2. void InsertSort1(int a[], int n)    //二路插入  
  3. {  
  4.     int first, last;  
  5.     first = last = 0;  
  6.     int *b = new int[n];  
  7.     b[0] = a[0];  
  8.     for (int i = 1; i < n; i++)  
  9.     {  
  10.         if (a[i] < b[first])  
  11.         {  
  12.             first = (first - 1 + n) % n;   //first的变化必须这样写  
  13.             b[first] = a[i];  
  14.         }  
  15.         else if (a[i] >= b[last])  
  16.         {  
  17.             last++;     //有的人这样写:last=(last+1)%n,其实没必要,last是不会超过n-1的。  
  18.             b[last] = a[i];  
  19.         }  
  20.         else  
  21.         {  
  22.             int k;  
  23.             for (k = last+1; a[i] < b[(k-1+n)%n]; k=(k-1+n)%n)     // 使用直接插入  
  24.                 b[k] = b[(k - 1 + n)%n];  
  25.             b[k] = a[i];  
  26.             last++;  
  27.         }  
  28.     }  
  29.     for (int i = 0; i < n; i++)  
  30.         a[i] = b[(i + first) % n];  
  31.     delete[]b;  
  32. }  
  33. </span>  

显然,在对一路二分插入时,更高效,代码如下:
代码二:在二分查找时,我们选择左闭右开的区间。若是无法理解折半查找的过程,强烈推荐看下:插入排序:直接插入,交换插入,折半插入
[cpp] view plain copy
  1. <span style="font-size:25px;">  
  2. void InsertSort2(int a[], int n)    //二路插入  
  3. {  
  4.     int first, last;  
  5.     first = last = 0;  
  6.     int *b = new int[n];  
  7.     b[0] = a[0];  
  8.     for (int i = 1; i < n; i++)  
  9.     {  
  10.         if (a[i] < b[first])  
  11.         {  
  12.             first = (first - 1 + n) % n;  
  13.             b[first] = a[i];  
  14.         }  
  15.         else if (a[i]>=b[last])  
  16.         {  
  17.             last++;  
  18.             b[last] = a[i];  
  19.         }  
  20.         else  
  21.         {  
  22.             int low, high, mid, d;  
  23.             low = first, high = last;  
  24.             while (low != high)      //折半查找  
  25.             {  
  26.                 d = (high-low+n) % n;    //元素个数  
  27.                 mid = (low + d / 2) % n;    //中间位置  
  28.                 if (a[i] < b[mid])  
  29.                     high = mid;  
  30.                 else  
  31.                     low = (mid + 1) % n;  
  32.             }  
  33.             for (int k = last + 1; k != low; k = (k - 1 + n) % n)  //移动元素  
  34.                 b[k] = b[(k - 1 + n) % n];  
  35.             b[low] = a[i];  
  36.             last++;  
  37.         }  
  38.     }  
  39.     for (int i = 0; i < n; i++)  
  40.         a[i] = b[(i + first) % n];  
  41.     delete[]b;  
  42. }  
  43. </span>  

update: 2014-6-2 0:11
下面给出一测试代码,详细观察数组b的变化情况,
[cpp] view plain copy
  1. #include<stdio.h>  
  2. #include<stdlib.h>  
  3. #include<string.h>  
  4. void printArray(int a[], int n)   //打印数组   
  5. {  
  6.     for (int i = 0; i<n; i++)  
  7.         printf("%-4d", a[i]);  
  8.     printf("\n");  
  9. }  
  10. void InsertSort2(int a[], int n)    //二路插入  
  11. {  
  12.     int first, last;  
  13.     first = last = 0;  
  14.     int *b = new int[n];  
  15.     memset(b,0,n*sizeof(int));   //数组b的内存空间清零  
  16.     b[0] = a[0];  
  17.     printf("数组下标:\n");  
  18.     for (int i = 0; i < n; i++)  
  19.         printf("%-4d",i);  
  20.     printf("\n");  
  21.     printf("数组b的变化\n");  
  22.     for (int i = 1; i < n; i++)  
  23.     {  
  24.         printArray(b, n);  
  25.         if (a[i] < b[first])  
  26.         {  
  27.             first = (first - 1 + n) % n;  
  28.             b[first] = a[i];  
  29.         }  
  30.         else if (a[i]>=b[last])  
  31.         {  
  32.             last++;  
  33.             b[last] = a[i];  
  34.         }  
  35.         else  
  36.         {  
  37.             int low, high, mid, d;  
  38.             low = first, high = last;  
  39.             while (low != high)      //折半查找  
  40.             {  
  41.                 d = (high-low+n) % n;    //元素个数  
  42.                 mid = (low + d / 2) % n;    //中间位置  
  43.                 if (a[i] < b[mid])  
  44.                     high = mid;  
  45.                 else  
  46.                     low = (mid + 1) % n;  
  47.             }  
  48.             for (int k = last + 1; k != low; k = (k - 1 + n) % n)  
  49.                 b[k] = b[(k - 1 + n) % n];  
  50.             b[low] = a[i];  
  51.             last++;  
  52.         }  
  53.     }  
  54.     printArray(b,n);  
  55.     for (int i = 0; i < n; i++)  
  56.         a[i] = b[(i + first) % n];  
  57.     delete[]b;  
  58. }  
  59. int main()  
  60. {  
  61.     const int N = 6;  
  62.     int a[N];  
  63.     srand((unsigned)time(NULL));  
  64.     printf("原数组a\n");  
  65.     for (int i = 0; i < N; i++)  
  66.     {  
  67.         a[i] = rand() % 100;  
  68.         printf("%-4d",a[i]);  
  69.     }  
  70.     printf("\n");  
  71.     InsertSort2(a, N);  
  72.     printf("经排序后的数组a\n");  
  73.     printArray(a, N);  
  74.     printf("\n");  
  75.     system("pause");  
  76.     return 0;  
  77. }  
某一次的运行结果是这样的:


小结:
  1. 这里我们使用的区间是左闭右开的,这是为了方便后面循环的终止。
  2. 关于first和last的移动,大家画下图,很容易明白。从上面的运行结果可以看出,last是从0开始递增的,且不会超过n-1,这一点是显然的。
  3. 在往二路插入时,是不需要移动元素的,这就是二路插入相对于前三种改进的地方。
  4. 若a[0]即是最小或最大的元素,则退化为直接插入,此时无法减少移动次数。
  5. 在代码二中,关于元素个数d=(high-low+n)%n,要注意:由于这里选取的是左闭右开的区间[low,high),好比区间[1,2)中整数个数是2-1=1,但在区间[1,2]中整数个数是2-1+1=2,所以这里是high-low,后面的+n,你懂的。若是写成d=(high-low+1+n)%n,会进入死循环,你可以试一下。


代码就是折腾,多折腾才有进步!

转载请注明出处,本文地址:http://blog.csdn.net/zhangxiangdavaid/article/details/27958143

若是有所帮助,顶一个哦!


    所有内容的目录
    • CCPP Blog 目录