数据结构与算法-第八章 插入排序

基本思想

在玩扑克牌时,我们在抓取下一张牌时,总会插入到我们的手牌中合适的位置,让我们的手牌保持从大到小的顺序,这就是插入排序.

	每步将一个待排序的对象,按其关键码的大小,插入到前面已经排好序的一组对象的适当位置上,直到对象全部插入为止.
	即边插入边排序,保证子序列中随时都是排好序的.
基本操作: 有序插入
  • 在有序序列中插入一个元素,保持序列有序,有序长度不断增加.
  • 起初, a[0]是长度为1的子序列,然后逐一将a[1]至a[n-1]插入到有序子序列中.

插入排序的种类

根据找到插入位置的不同方法,我们可以将插入排序分为:
	顺序法定位插入位置------ 直接插入排序
	二分法定位插入位置------ 二分插入排序
	缩小增量多遍插入排序------ 希尔排序
直接插入排序
  1. 复制插入元素
x为记录需要插入的元素,而需要插入的元素存储在a[i],:
x=a[i];
  1. 记录后移,查找插入位置
j指针指向a[i]的前一个元素,进行循环查找适合x插入的位置:
for (j = i-1; j>=0 && x<a[j]; j--)
	a[j+1] = a[j];
  1. 插入到正确位置:
a[j+1] = x;
直接插入排序, 使用"哨兵"
1. 复制为哨兵
	L.r[0] = L.r[i];
2. 记录后移,查找插入位置
	for (j = i-1; L.r[0].key<L.r[j].key; j--)
		L.r[j+1] = L.r[j];
3. 将哨兵位置上记录的关键字插入到正确位置:
	L.r[j+1] = L.r[0];
	
若在执行第1步之前先判断:需要插入的元素是否比排好序的序列中最后一个元素大;如果大的话就不用移动需要插入的元素了.

/*
 * 直接插入排序
 */
void InsertSort(SqList L) {
    int i,j;
    for (i = 2; i < L.length; i++) {
        if (L.r[i].key < L.r[i-1].key) {        // 若"<",需要将L.r[i]插入到有序表中
            L.r[0] = L.r[i];                    // 复制为哨兵
        }
        for (j = i-1; L.r[0] < L.r[j]; j--) {
            L.r[j+1] = L.r[j];                  // 记录后移
        }
        L.r[j+1] = L.r[0];                      // 插入到正确位置
    }
}
直接插入排序------性能分析(稳定排序)
实现排序的基本操作有两个:
1. "比较"序列中两个关键字的大小;
2. "移动"记录

最好的情况:关键字在记录中顺序有序
	比较的次数: n-1
	移动的次数: 0
最坏的情况: 关键字在记录中逆序有序
	比较的次数: (n+2)(n-1)/2
	移动的次数: (n+4)(n-1)/2
平均情况
	比较次数: (n+2)(n-1)/4
	移动次数: (n+6)(n-1)/4

原始数据越有序,排序速度越快
最坏情况下Tw(n) = O(n^2)
平均情况下Te(n) = O(n^2)

空间复杂度,需要一个哨兵位: O(1)

要提高查找速度:
	减少元素的比较次数
	减少元素的移动次数
折半插入排序-------引入折半查找法
/*
 * 折半插入排序
 */
void BInsertSort(SqList L) {
    for (int i = 2; i <= L.length; i++) {       // 依次插入第2~第n个元素
        L.r[0] = L.r[i];                        // 当前插入元素存到哨兵位
        int low = 1;
        int high = i-1;
        while (low < high) {
            int mid = (low + high) / 2;
            if (L.r[0].key < L.r[mid].key)
                high = mid -1;
            else low = mid + 1;
        }                                       // 循环结束,high+1则为插入位置
        for (int j = i-1; j >= high+1; j--) {   // 移动元素
            L.r[j+1] = L.r[j];                  
        }
        L.r[high+1] = L.r[0];                   // 插入到正确位置
    }
}
折半插入排序------算法分析
  • 折半查找比顺序查找要快,所以折半插入排序就平均性能来说比直接插入排序要快;
  • 它所需要的关键码比较次数与待排序对象序列的初始排列无关,仅依赖于对象个数.在插入第i个对象时,需要经过logi+1次关键码比较,才能确定它应插入的位置;
  • 当n较大时,总关键码比较次数比直接插入排序的最坏情况要好,但比其最好情况要差;
  • 在对象的初始排列已经按关键码排好序或接近有序时,直接插入排序比折半插入排序执行的关键码比较次数要少;
  • 折半插入排序的对象移动次数与直接插入排序相同,依赖于对象的初始排列;
  • 减少了比较次数,但没有减少移动次数;
  • 平均性能优于直接插入排序;
希尔排序
基本思想: 
	先将整个待排记录序列分割成若干子序列,分别进行直接插入排序;待整个序列中的记录"基本有序",再对全体记录进行依次直接插入排序.
	1. 定义增量序列D: Dm>Dm-1>...>D1; 如D3=5, D2=3, D1=1;
	2. 对每个Dk进行"Dk-间隔"插入排序(k=m,m-1,...,1);
	
希尔排序的特点就是:
	1. 一次移动,移动位置较大,跳跃式地接近排序后的最终位置;
	2. 最好一次只需少量移动;
	3. 增量序列必须是递减的,最后一个必须是1;
	4. 增量序列应该是互质的;

void ShellSort(SqList &L, int dlta[], int t) { // Dk值依次存在dlta[t]中
    // 按增量序列dlta[0...t-1]对顺序表L作希尔排序.
    for (int k = 0; k < t; k++)
        ShellInsert(L, dlta[k]);    //一趟增量为dlta[k]的插入排序
}// ShellSort

void ShellInsert(SqList &L, int Dk) {
    int i,j;
    // 对顺序表L进行一趟增量为Dk的Shell排序,Dk为步长因子
    for (i = Dk+1; i <= L.length; i++)
        if (L.r[i].key < L.r[i-Dk].key) {
            L.r[0] = L.r[i];
            for (j = i-Dk; j>0 && (L.r[0].key < L.r[j].key); j = j-Dk)
                L.r[j+dk] = L.r[j];
            L.r[j+Dk] = L.r[0];
        }
}
希尔排序算法分析
希尔排序算法效率与增量序列的取值有关
	Hibbard增量序列:Dk=2^k- 1 ------ 相邻元素互质
	Sedgewick增量序列: {1, 5, 19, 41, 109...} ------ 9*4^i-9*2^i+14^i-3*2^i+1

希尔排序算法是一种不稳定的排序算法;
	时间复杂度是n和d的函数:
		O(n^1.25)~O(1.6n^1.25) ------ 经验公式;
	空间复杂度为O(1);
	是一种不稳定的排序;
	如何选择最佳d增量序列,目前尚未解决,但最后一个增量序列值必须为1,无除了1之外的公因子;
	不适合在链式存储结构上实现;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值