数据结构复习-插入排序

插入排序是一种简单直观的排序方法,其基本思想在于每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子序列中,直到全部记录插入完成。
由插入排序的思想可以引申出三个重要的排序算法:直接插入排序、折半插入排序和希尔插入排序。

1. 直接插入排序

假设在排序过程中,待排序表L[1...n]在某次排序过程中的某一时刻状态如下:

有序序列L(i)L(i)无序序列L[i+1...n]

为了实现将元素L(i)插入到已有序的子序列L[1...i-1]中,我们需要执行以下操作:
1)查找出L(i)在L[1...i-1]中的插入位置k。
2)将L[k...i-1]中所有元素全部后移一个位置。
3)将L(i)复制到L(k)。
为了实现对L[1...n]的排序,可以将L(2)~L(n)依次插入到前面已经排好序的子序列中,初始假定L[1]是一个已排好序的子序列。上述操作执行n-1次就能得到一个有序的表。插入排序在实现上通常采用就地排序(空间复杂度为O(1)),因而在从后向前的比较过程中,需要反复把已排序元素逐步向后挪位,为新元素提供插入空间。
下面是直接插入排序的代码。

void InsertSort(ElemType A[], int n) {
    int i,j;
    for(i=2; i<=n; i++)                 
        if(A[i].key < A[i-1].key) {
            A[0] = A[i];
            for(j=i-1; A[0].key<A[j].key; --j)
                A[j+1] = A[j];
            A[j+1] = 0;
        }
}

直接插入排序算法的性能分析如下:
空间效率:仅使用了常数个辅助单元,因而空间复杂度为O(1)。
时间效率:在排序过程中,向有序子表中逐个地插入元素的操作进行了n-1趟,每趟操作都分为比较关键字和移动元素,而比较次数和移动次数取决于待排序表的初始状态。
在最好情况下,表中元素已经有序,此时每插入一个元素,都只需比较一次而不用移动元素,因而时间复杂度为O(n)。
在最坏情况下,表中元素顺序刚好与排序结果中元素顺序相反(逆序)时,总的比较次数达到最大,为sum(i),i=2,3...,n。总的移动次数也达到最大,为sum(i+1),i=2,3...,n。

平均情况下,考虑待排序表中元素是随机的,此时可以取上述最好与最坏情况的平均值作为平均情况下的时间复杂度,总的比较次数与总的移动次数均约为n^2/4。

由此,直接插入排序算法的时间复杂度为O(n^2)。虽然折半插入排序算法的时间复杂度也有O(n^2),但对于数据量比较小的排序表,折半插入排序往往能表现出很好的性能。
稳定性:由于每次插入元素时总是从后向前先比较再移动,所以不会出现相同元素相对位置发生变化的情况,即直接插入排序是一个稳定的排序方法。
适用性:直接插入排序算法适用于顺序存储和链式存储的线性表。当为链式存储时,可以从前往后查找指定元素的位置。

2. 折半插入排序

从前面的直接插入排序算法中,每趟插入的过程中,都进行了两项工作:①从前面的子表中查找出待插入元素应该被插入的位置;②给插入位置腾出空间,将待插入元素复制到表中的插入位置。注意到该算法中,总是边比较边移动元素,下面将比较和移动操作分离出来,即先折半查找出元素的待插入位置,然后再统一地移动待插入位置之后的所有元素。当排序表为顺序存储的线性表时,可以对直接插入排序算法作如下改进:
由于是顺序存储的线性表,所以查找有序子表时可以用折半查找来实现。在确定出待插入位置后,就可以统一地向后移动元素了。算法如下:

void InsertSort(ElemType A[],int n) {
    int i,j,low,high,mid;
    for(i=2; i<=n; i++) {
        A[0] = A[i];
        low=1; high=i-1;
        while(low<=high) {
            mid = (low + high)/2;
            if(A[mid.key > A[0].key])
                high = mid - 1;
            else 
                low = mid + 1;
        }
        for(j=i-1; j>=high+1; --j)
            A[j+1]=A[j];
        A[high+1] = A[0];
    }
}

从上述算法中,不难看出折半插入排序仅仅是减少了比较元素的次数,约为O(nlog2(n)),该比较次数与待排序表的初始状态无关,仅取决于表中的元素个数n;而元素的移动次数没有改变,它依赖于待排序的初始状态。因此,折半插入排序的时间复杂度仍为O(n^2)。折半插入排序是一个稳定的排序方法。

3. 希尔排序

直接插入排序算法适用于基本有序的排序表和数据量不大的排序表。基于这两点,提出了希尔排序,又称为缩小增量排序。
希尔排序的基本思想是:先将待排序表分割成若干个形如L[i,i+d,i+2d,...,i+kd]的“特殊”子表,分别进行直接插入排序,当整个表中元素已呈“基本有序”时,再对全体记录进行一次直接插入排序。希尔排序的排序过程如下:
先取一个小于n的步长d1,把表中全部记录分成d1组,所有距离为d1的倍数的记录放在同一组中,在各组中进行直接插入排序;然后取第二个步长d2 < d1,重复上述过程,直到所取到的dt=1,即所有记录已放在同一组中,再进行直接插入排序,由于此时已经具有较好的局部有序性,故可以很快得到最终结果,到目前为止,尚未求得一个最好的增量序列,希尔提出的方法是d1=n/2,dn+1=di/2,并且最后一个增量等于1。
希尔排序算法如下:

void ShellSort(ElemType A[], int n) {
    for(dk=n/2; dk>=1; dk=dk/2) {
        for(i=dk+1; i<=n; ++i) {
        if(A[i].key<A[i-dk].key) {
            A[0] = A[i];
            for(j=i-dk; j>0&&A[0].key<A[j].key; j-=dk) {
                A[j+dk] = A[j];
            }
            A[j+dk]=A[0];
        }
    }
}

希尔排序算法的性能分析如下:
空间效率:仅使用了常量个辅助单元,因而空间复杂度为O(1)。
时间效率:由于希尔排序的时间复杂度依赖于增量序列的函数,这涉及数学上尚未解决的难题,所以其时间复杂度分析比较困难。当n在某个特定范围时,希尔排序的时间复杂度约为
O(n^1.3)。在最坏情况下希尔排序的时间复杂度为O(n^2)。
稳定性:当相同关键字的记录被划分到不同的子表时,可能会改变它们之间的相对次序,因此,希尔排序是一个不稳定的排序算法。
适用性:希尔排序算法仅适用于当线性表为顺序存储的情况。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值