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


    基本思想

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

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

    插入排序的种类

    根据找到插入位置的不同方法,我们可以将插入排序分为:
    	顺序法定位插入位置------ 直接插入排序
    	二分法定位插入位置------ 二分插入排序
    	缩小增量多遍插入排序------ 希尔排序
    
    • 1
    • 2
    • 3
    • 4
    直接插入排序
    1. 复制插入元素
    x为记录需要插入的元素,而需要插入的元素存储在a[i],:
    x=a[i];
    
    • 1
    • 2
    1. 记录后移,查找插入位置
    j指针指向a[i]的前一个元素,进行循环查找适合x插入的位置:
    for (j = i-1; j>=0 && x<a[j]; j--)
    	a[j+1] = a[j];
    
    • 1
    • 2
    • 3
    1. 插入到正确位置:
    a[j+1] = x;
    
    • 1
    直接插入排序, 使用"哨兵"
    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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    直接插入排序------性能分析(稳定排序)
    实现排序的基本操作有两个:
    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)
    
    要提高查找速度:
    	减少元素的比较次数
    	减少元素的移动次数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    折半插入排序-------引入折半查找法
    /*
     * 折半插入排序
     */
    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];                   // 插入到正确位置
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    折半插入排序------算法分析
    • 折半查找比顺序查找要快,所以折半插入排序就平均性能来说比直接插入排序要快;
    • 它所需要的关键码比较次数与待排序对象序列的初始排列无关,仅依赖于对象个数.在插入第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];
            }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    希尔排序算法分析
    希尔排序算法效率与增量序列的取值有关
    	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之外的公因子;
    	不适合在链式存储结构上实现;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
  • 相关阅读:
    790.多米诺和托米诺平铺(DP)
    《Python+Kivy(App开发)从入门到实践》自学笔记:简单UX部件——TextInput输入框
    向GB28181平台注册时总是注册失败,接入不到视频监控平台的问题排查
    Python生成正态分布的随机数
    【Java基础】- HttpURLConnection详解
    如何开发你的第一个Flutter App?
    Mac OS 使用ScreenCaptureKit进行窗口抓取和系统声音抓取
    [山东科技大学OJ]1337 Problem A: 编写函数:字符串的查找字符 之一 (Append Code)
    队列的各个函数的实现
    鸿蒙状态栏设置
  • 原文地址:https://blog.csdn.net/sjn2212297386/article/details/127749859