首页 > 代码库 > 三种插入排序的分析

三种插入排序的分析

插入排序(Insertion Sort)的基本思想是:每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子文件中的适当位置,直到全部记录插入完成为止。

一、直接插入排序

直接插入排序(insert sorting)思想:当插入第i个元素时,前面的v[0],v[1],v[2]......v[i-1],已经排好序了.这时用v[i]的插入码与v[i-1],v[i-2],......排序码进行比较,找到插入的位置即插入v[i],原来位置上的元素从后向前依次后移。

template<class T>
void InsertSort(T a[],int n)
{
     for (int i = 1;i < n;++i)
     {
          int tmp = a[i];

          int j;
          for (j = i-1;j >= 0&&a[j]>tm p;--j)
               a[j+1] = a[j];
          a[j+1] = tmp;
 
     }
}

时间复杂度:
从时间分析,首先外层循环要进行n-1次插入,每次插入最少比较一次(正序),移动两次;最多比较i次,移动i+2次(逆序)(i=1,2,…,n-1)。若分别用Cmin ,Cmax 和Cave表示元素的总比较次数的最小值、最大值和平均值,用Mmin ,Mmax 和Mave表示元素的总移动次数的最小值、最大值和平均值,则上述直接插入算法对应的这些量为:
Cmin=n-1 Mmin=2(n-1)
Cmax=1+2+…+n-1=(n^2-n)/2 Mmax=3+4+…+n+1=(n^2+3n-4)/2
Cave=(n^2+n-2)/4 Mmax=(n^2+7n-8)/4
因此,直接插入排序的时间复杂度为O(n^2)。

由上面对时间复杂度的分析可知,当待排序元素已从小到大排好序(正序)或接近排好序时,所用的比较次数和移动次数较少;当待排序元素已从大到小排好序(逆序)或接近排好序时,所用的比较次数和移动次数较多,所以插入排序更适合于原始数据基本有序(正序)的情况.

插入法虽然在最坏情况下复杂性为O(n^2),但是对于小规模输入来说,插入排序法是一个快速的排序法。许多复杂的排序法,在规模较小的情况下,都使用插入排序法来进行排序,比如快速排序。

空间复杂度:
首先从空间来看,它只需要一个元素的辅助空间,用于元素的位置交换O(1)。

稳定性:
插入排序是稳定的,因为具有同一值的元素必然插在具有同一值得前一个元素的后面,即相对次序不变.

适用情况:
插入排序是一种简单的排序方法,他不仅适用于顺序存储结构(数组),而且适用于链接存储结构,不过在链接存储结构上进行直接插入排序时,不用移动元素的位置,而是修改相应的指针。

二、二分插入法

二分(折半)插入(Binary insert sort)排序基本思想:设在数据表中有一个元素序列v[0],v[1],v[2]......v[n].其中v[0],v[1],v[2]......v[i-1]是已经排好序的元素。在插入v[i]。利用折半搜索寻找v[i]的插入位置。

二分插入排序是一种稳定的排序。当n较大时,总排序码比较次数比直接插入排序的最差情况好得多,但比最好情况要差,所元素初始序列已经按排序码接近有序时,直接插入排序比二分插入排序比较次数少。二分插入排序元素移动次数与直接插入排序相同,依赖于元素初始序列。

template<class T>
void BinaryInsertSort(T a[],int n)
{
    for (int i = 1;i < n;++i)
    {
         int left = 0,
         right = i-1;
         int tmp = a[i];
         while (left <= right)
        {
              int middle = (left+right)/2;
              if (a[i]<a[middle])
                   right = middle-1;
              else
                   left = middle+1;   
        }
        int j;
        for (j = i-1;j>=left;--j)
             a[j+1] = a[j];
       a[j+1] = tmp;
    }
}

三、希尔排序

基本思想:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为dl的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<;…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。

增量序列的选择:

Shell排序的执行时间依赖于增量序列。
好的增量序列的共同特征:
① 最后一个增量必须为1;
② 应该尽量避免序列中的值(尤其是相邻的值)互为倍数的情况。
有人通过大量的实验,给出了目前较好的结果:当n较大时,比较和移动的次数约在n到1.6n之间。
参考代码:
template<class T>
void ShellSort(T a[],int n)
{
    for (int gap = n/2;gap >= 1;gap/=2)
    {
         for (int i = gap;i < n;++i)
         {
              int tmp = a[i];
              int j;
              for (j = i-gap;j >= 0&&a[j] > tmp;j-=gap)
                    a[j+gap] = a[j];
              a[j+gap] = tmp;
         }                   
    }
}

性能分析:

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

稳定性:

希尔排序是不稳定的。