美文网首页
数据结构与算法(十六):排序算法

数据结构与算法(十六):排序算法

作者: 顶级蜗牛 | 来源:发表于2024-03-22 09:18 被阅读0次

    相关文献:
    数据结构与算法(一):基础理论
    数据结构与算法(二):线性表的实现
    数据结构与算法(三):线性表算法设计练习
    数据结构与算法(四):斐波那契数列
    数据结构与算法(五):LRU
    数据结构与算法(六):栈
    数据结构与算法(七):栈/队列的算法解题思想
    数据结构与算法(八):队列
    数据结构与算法(九):树形结构/二叉树/线索化二叉树
    数据结构与算法(十):哈夫曼树
    数据结构与算法(十一):图形结构
    数据结构与算法(十二):图的应用-最小生成树-Prim/Kruskal
    数据结构与算法(十三):图的应用-最短路径-Dijkstra/Floyd
    数据结构与算法(十四):图的应用-拓扑排序/关键路径
    数据结构与算法(十五):查找算法-顺序查找/二分查找/二叉搜索树/平衡二叉树/散列表查找
    数据结构与算法(十六):排序算法

    本文掌握知识点:
    1.冒泡排序算法
    2.选择排序算法
    3.直接插入排序算法
    4.希尔排序算法
    5.堆排序算法
    6.归并排序算法
    7.快速排序算法

    前言

    假设含有n个记录的序列为(r1,r2,.....,rn). 其相应的关键字分别为{k1,k2,......,kn}. 需确定1,2,......,n 的⼀种排序p1,p2,......pn. 使其相应的关键字满⾜kp1 <= kp2 <= ...... <= kpn ⾮递减(或⾮递增)关系. 即使得到序列成为⼀个按关键字有序的序列(rp1,rp2,...,rpn).这样得出操作称为排序

    排序分类:

    • 内排序:是在排序整个过程中,待排序的所有记录全部被放置在内存中;
    • 外排序:由于排序的记录个数太多,不能同时放置在内存,整个排序过程需要在内外存之间多次交换数据才能进⾏。

    数据结构设计:
    (下面的算法都以1作为索引的开始;0的位置作为临时变量)

    #include <stdio.h>
    #include <string.h>
    #include <ctype.h>
    #include <stdlib.h>
    #include <math.h>
    #include <time.h>
    
    #define OK 1
    #define ERROR 0
    #define TRUE 1
    #define FALSE 0
    
    typedef int Status;
    
    //1.排序算法数据结构设计
    //⽤于要排序数组个数最⼤值,可根据需要修改
    #define MAXSIZE 10000
    typedef struct {
     //⽤于存储要排序数组,r[0]⽤作哨兵或临时变量
     int r[MAXSIZE+1];
     //⽤于记录顺序表的⻓度
     int length;
    }SqList
    
    //2.排序常⽤交换函数实现
    //交换L中数组r的下标为i和j的值
    void swap(SqList *L ,int i ,int j) {
     int temp=L->r[i];
     L->r[i]=L->r[j];
     L->r[j]=temp;
    }
    
    //3.数组打印
    void print(SqList L) {
     int i;
     for(i=1;i< L.length ;i++)
     printf("%d,",L.r[i]);
     printf("%d",L.r[i]);
     printf("\n");
    }
    

    一、冒泡排序

    冒泡排序(Bubble Sort) ⼀种交换排序,基本思想: 两两⽐较相邻的记录的关键字,如果反序则交换,直到没有反序的记录为⽌。

    冒泡排序时间复杂度O(n²).

    初级版本

    可以发现这只是个简单的交换排序,已经交换的元素对后面的完全没有帮助。

    //4. 冒泡排序-对顺序表L进行交换排序(冒泡排序初级版本)
    void BubbleSort0(SqList *L){
        int i,j;
        for (i = 1; i < L->length; i++) {
            for (j = i+1; j <= L->length; j++) {
                if(L->r[i] > L->r[j])
                    swap(L, i, j);
            }
        }   
    }
    // 严格意义上来说,这不算是冒泡排序,是个交换排序
    
    完成版本 完成版本
    //5.冒泡排序-对顺序表L作冒泡排序(正宗冒泡排序算法)
    void BubbleSort1(SqList *L){
        int i,j;
        for (i = 1; i < L->length; i++) {
            //注意:j是从后面往前循环
            for (j = L->length-1; j>=i; j--) {
                //若前者大于后者(注意与上一个算法区别所在)
                if(L->r[j]>L->r[j+1])
                    //交换L->r[j]与L->r[j+1]的值;
                    swap(L, j, j+1);
            }
        }
    }
    
    最终优化版本
    //6.冒泡排序-对顺序表L冒泡排序进行优化
    void BubbleSort2(SqList *L){
        int i,j;
        //flag用作标记
        Status flag = TRUE;
        
        //i从[1,L->length) 遍历;
        //如果flag为False退出循环. 表示已经出现过一次j从L->Length-1 到 i的过程,都没有交换的状态;
        for (i = 1; i < L->length && flag; i++) {
            //flag 每次都初始化为FALSE
            flag = FALSE;
            
            for (j = L->length-1; j>=i; j--) {
                if(L->r[j] > L->r[j+1]){
                //交换L->r[j]和L->r[j+1]值;
                swap(L, j, j+1);
                //如果有任何数据的交换动作,则将flag改为true;
                flag=TRUE;
                }
            }
        }
    }
    

    二、选择排序算法

    简单排序算法(Simple Selecton Sort) 就是通过n-i次关键词⽐较,从n-i+1个记录中找出关键字最⼩的记录,并和第i(1<=i<=n) 个记录进⾏交换。

    选择排序时间复杂度:O(n²)

    ......

    //7.选择排序--对顺序表L进行简单选择排序
    void SelectSort(SqList *L){
        int i,j,min;
    
        for (i = 1; i < L->length; i++) {
            //① 将当前下标假设为最小值的下标
            min = i;
            //② 循环比较i之后的所有数据
            for (j = i+1; j <= L->length; j++) {
                //③ 如果有小于当前最小值的关键字,将此关键字的下标赋值给min
                if (L->r[min] > L->r[j]) {
                    min = j;
                }
            }
            
            //④ 如果min不等于i,说明找到了最小值,则交换2个位置下的关键字
            if(i!=min)
                swap(L, i, min);
        }
    }
    

    三、直接插入排序算法

    直接插⼊排序算法(Stight Inserton Sort)的基本操作是将⼀个记录插⼊到已经排好序的有序表中,从⽽得到⼀个新的,记录数增1的有序表;

    直接插入排序时间复杂度是 O(n²)

    高效的地方:部分有序,小规模数据

    ......

    //8.直接插入排序算法--对顺序表L进行直接插入排序
    void InsertSort(SqList *L){
        int i,j;
        //L->r[0] 哨兵 可以把temp改为L->r[0]
        int temp=0;
        
        //假设排序的序列集是{0,5,4,3,6,2};
        //i从2开始的意思是我们假设5已经放好了. 后面的牌(4,3,6,2)是插入到它的左侧或者右侧
        for(i=2;i<=L->length;i++)
        {
            //需将L->r[i]插入有序子表
            if (L->r[i]<L->r[i-1])
            {
                //设置哨兵 可以把temp改为L->r[0]
                temp = L->r[i];
                for(j=i-1;L->r[j]>temp;j--)
                        //记录后移
                        L->r[j+1]=L->r[j];
                
                //插入到正确位置 可以把temp改为L->r[0]
                L->r[j+1]=temp;
            }
        }
    }
    

    四、希尔排序算法

    希尔排序是有时代意义的,在之前很长一段时间内排序算法的时间复杂度都突破不了O(n²)。

    希尔排序思想: 希尔排序是把记录按下标的⼀定增量分组,对每组使⽤直接插⼊排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减⾄1时,整个序列恰被分成⼀组,算法便终⽌。
    (希尔排序是在插入排序的基础上进行优化的)

    • 原理:在插⼊排序之前,将整个序列调整成基本有序. 然后再对全体序列进⾏⼀次直接插⼊排序。

    注意这样的分组排序后的序列并不是基本有序,而是局部有序。
    (基本有序更利于插入排序算法的高效性)

    局部有序与基本有序的区别

    希尔排序是如何分组的?如何排序的?

    分组 - 第一次得到基本有序

    将原序列分成了5组(可以根据自己的需要去分组),大的往后,小的往前交换了位置。

    分组 - 第二次得到基本有序

    将第一次基本有序的结果再进行分成2组,然后进行插入排序。

    最后一轮插入排序

    最后一轮增量increment缩减至1,将第二次基本有序的记过分成1组,进行插入排序,得到最后的结果。

    //9.希尔排序-对顺序表L希尔排序
    void shellSort(SqList *L){
        int i,j;
        int increment = L->length;
        //0,9,1,5,8,3,7,4,6,2
        //① 当increment 为1时,表示希尔排序结束
        do{
            //② 增量序列
            increment = increment/3+1;
            //③ i的待插入序列数据 [increment+1 , length]
            for (i = increment+1; i <= L->length; i++) {
                //④ 如果r[i] 小于它的序列组元素则进行插入排序,例如3和9. 3比9小,所以需要将3与9的位置交换
                if (L->r[i] < L->r[i-increment]) {
                    //⑤ 将需要插入的L->r[i]暂时存储在L->r[0].和插入排序的temp 是一个概念;
                    L->r[0] = L->r[i];
                    
                    //⑥ 记录后移
                    for (j = i-increment; j > 0 && L->r[0]<L->r[j]; j-=increment) {
                        L->r[j+increment] = L->r[j];
                    }
                    
                    //⑦ 将L->r[0]插入到L->r[j+increment]的位置上;
                    L->r[j+increment] = L->r[0];
                }
            }
        }while (increment > 1);
    }
    
    希尔排序时间复杂度

    五、堆排序算法

    注意:这里的堆指的是一种数据结构,并非我们理解的内存中的堆栈。

    1.了解堆结构

    堆是具有下⾯性质的完全⼆叉树:每个结点的值都⼤于或等于其左右孩⼦结点的值,称为⼤顶堆 如图1;或者每个结点的值都⼩于等于其左右孩⼦的结点的值,称为⼩顶堆 如图2.

    图1在数组中的存储:
    比如90节点左孩子位置在 2i = 2;右孩子的位置再2i+1=3
    比如70节点左孩子位置在 2i = 4;右孩子的位置再2i+1=5
    ... 如下图:

    图1在数组中的存储

    图2在数组中的存储:

    图2在数组中的存储
    2.堆排序(Heap Sort) 原理

    堆排序(Heap Sort) 就是利⽤堆(假设我们选择⼤顶堆)进⾏排序的算法.
    它的基本思想:
    ①将待排序的序列构成⼀个⼤顶堆,此时,整个序列的最⼤值就堆顶的根结点,将它移⾛(其实就是将其与堆数组的末尾元素交换, 此时末尾元素就是最⼤值);
    ②然后将剩余的n-1个序列重新构成⼀个队,这样就会得到n个元素的次⼤值, 如此重复执⾏,就能得到⼀个有序序列了

    当然我们一开始是不可能有这样的序列的。

    3.堆排序模拟

    ....中间过程省略...

    问题:
    • 如何由⼀个⽆序序列构建成⼀个堆?
    • 如何在输出堆顶元素后,调整剩余元素成为⼀个新的堆?

    堆排序思路:
    • 将⽆需序列构建成⼀个堆,根据升序降序需求选择⼤顶堆或⼩顶堆
    • 将堆顶元素与末尾元素交换,将最⼤元素”沉"到数组末端;
    • 重新调整结构,使其满⾜堆定义,然后继续交换堆顶元素与当前末尾元素,反复执⾏调整+交换步骤,直到整个序列有序

    4.代码实现
    //大顶堆调整函数;
    /*
     条件: 在L.r[s...m] 记录中除了下标s对应的关键字L.r[s]不符合大顶堆定义,其他均满足;
     结果: 调整L.r[s]的关键字,使得L->r[s...m]这个范围内符合大顶堆定义.
     */
    void HeapAjust(SqList *L,int s,int m){
        
        int temp,j;
        //① 将L->r[s] 存储到temp ,方便后面的交换过程;
        temp = L->r[s];
        
        //② j 为什么从2*s 开始进行循环,以及它的递增条件为什么是j*2
        //因为这是颗完全二叉树,而s也是非叶子根结点. 所以它的左孩子一定是2*s,而右孩子则是2s+1;(二叉树性质5)
        for (j = 2 * s; j <=m; j*=2) {
            
            //③ 判断j是否是最后一个结点, 并且找到左右孩子中最大的结点;
            //如果左孩子小于右孩子,那么j++; 否则不自增1. 因为它本身就比右孩子大;
            if(j < m && L->r[j] < L->r[j+1])
                ++j;
            
            //④ 比较当前的temp 是不是比较左右孩子大; 如果大则表示我们已经构建成大顶堆了;
            if(temp >= L->r[j])
                break;
            
            //⑤ 将L->[j] 的值赋值给非叶子根结点
            L->r[s] = L->r[j];
            //⑥ 将s指向j; 因为此时L.r[4] = 60, L.r[8]=60. 那我们需要记录这8的索引信息.等退出循环时,能够把temp值30 覆盖到L.r[8] = 30. 这样才实现了30与60的交换;
            s = j;
        }
        
        //⑦ 将L->r[s] = temp. 其实就是把L.r[8] = L.r[4] 进行交换;
        L->r[s] = temp;
    }
    
    
    //10.堆排序--对顺序表进行堆排序
    void HeapSort(SqList *L){
        int i;
       
        //1.将现在待排序的序列构建成一个大顶堆;
        //将L构建成一个大顶堆;
        //i为什么是从length/2.因为在对大顶堆的调整其实是对非叶子的根结点调整.
        for(i=L->length/2; i>0;i--){
            HeapAjust(L, i, L->length);
        }
        
        
        //2.逐步将每个最大的值根结点与末尾元素进行交换,并且再调整成大顶堆
        for(i = L->length; i > 1; i--){
            
            //① 将堆顶记录与当前未经排序子序列的最后一个记录进行交换;
            swap(L, 1, i);
            //② 将L->r[1...i-1]重新调整成大顶堆;
            HeapAjust(L, 1, i-1);
        }
    }
    

    六、归并排序算法

    归并排序(Merging Sort)就是利用归并的思想实现排序方法。原理是假设原序列含有n个记录,则可以看成n个有序的子序列。每个子序列的长度为1。然后两两合并,得到[n/2]个长度为2或1的有序子序列,再两两合并...如此重复操作,直到得到1个长度为n的有序序列为止,这种排序方法称之为两路归并排序法。

    归并排序法思想
    • 递归方式
    归并排序过程

    MSort函数拆完之后再去归并排序,往拆原路返回地归并Merge函数。

    ......

    //11.归并排序-对顺序表L进行归并排序
    //③ 将有序的SR[i..mid]和SR[mid+1..n]归并为有序的TR[i..n]
    void Merge(int SR[],int TR[],int i,int m,int n)
    {
        int j,k,l;
        //1.将SR中记录由小到大地并入TR
        for(j=m+1,k=i;i<=m && j<=n;k++)
        {
            if (SR[i]<SR[j])
                TR[k]=SR[i++];
            else
                TR[k]=SR[j++];
        }
        
        //2.将剩余的SR[i..mid]复制到TR
        if(i<=m)
        {
            for(l=0;l<=m-i;l++)
                TR[k+l]=SR[i+l];
        }
        
        //3.将剩余的SR[j..mid]复制到TR
        if(j<=n)
        {
            for(l=0;l<=n-j;l++)
                TR[k+l]=SR[j+l];
        }
    }
    
    
    //② 将SR[s...t] 归并排序为 TR1[s...t];
    void MSort(int SR[],int TR1[],int low, int hight){
        int mid;
        int TR2[MAXSIZE+1];
        
        if(low == hight)
            TR1[low] = SR[low];
        else{
            //1.将SR[low...hight] 平分成 SR[low...mid] 和 SR[mid+1,hight];
            mid = (low + hight)/2;
            //2. 递归将SR[low,mid]归并为有序的TR2[low,mid];
            MSort(SR, TR2, low, mid);
            //3. 递归将SR[mid+1,hight]归并为有序的TR2[mid+1,hight];
            MSort(SR, TR2, mid+1, hight);
            //4. 将TR2[low,mid] 与 TR2[mid+1,hight], 归并到TR1[low,hight]中
            Merge(TR2, TR1, low, mid, hight);
        }
    }
    
    //① 对顺序表L进行归并排序
    void MergeSort(SqList *L){
       
        MSort(L->r,L->r,1,L->length);
    }
    

    归并排序算法还能优化成非递归的方式嘛?可以的

    来看看递归和非递归的区别:

    • 非递归方式
    //12.归并排序(非递归)-->对顺序表L进行非递归排序
    //对SR数组中相邻长度为s的子序列进行两两归并到TR[]数组中;
    void MergePass(int SR[],int TR[],int s,int length){
      
        int i = 1;
        int j;
        
        //①合并数组
        //s=1 循环结束位置:8 (9-2*1+1=8)
        //s=2 循环结束位置:6 (9-2*2+1=6)
        //s=4 循环结束位置:2 (9-2*4+1=2)
        //s=8 循环结束位置:-6(9-2*8+1=-6) s = 8时,不会进入到循环;
        while (i<= length-2*s+1) {
            //两两归并(合并相邻的2段数据)
            Merge(SR, TR, i, i+s-1, i+2*s-1);
            i = i+2*s;
            
            /*
             s = 1,i = 1,Merge(SR,TR,1,1,2);
             s = 1,i = 3,Merge(SR,TR,3,3,4);
             s = 1,i = 5,Merge(SR,TR,5,5,6);
             s = 1,i = 7,Merge(SR,TR,7,7,8);
             s = 1,i = 9,退出循环;
             */
            
            /*
             s = 2,i = 1,Merge(SR,TR,1,2,4);
             s = 2,i = 5,Merge(SR,TR,5,6,8);
             s = 2,i = 9,退出循环;
             */
            
            /*
             s = 4,i = 1,Merge(SR,TR,1,4,8);
             s = 4,i = 9,退出循环;
             */
        }
        
        //②如果i<length-s+1,表示有2个长度不等的子序列. 其中一个长度为length,另一个小于length
        // 1 < (9-8+1)(2)
        //s = 8时, 1 < (9-8+1)
        if(i < length-s+1){
            //Merge(SR,TR,1,8,9)
            Merge(SR, TR, i, i+s-1, length);
        }else{
            //③只剩下一个子序列;
            for (j = i; j <=length; j++) {
                TR[j] = SR[j];
            }
        }
    }
    
    void MergeSort2(SqList *L){
        int *TR = (int *)malloc(sizeof(int) * L->length);
        int k = 1;
        //k的拆分变换是 1,2,4,8;
        while (k < L->length) {
            //将SR数组按照s=2的长度进行拆分合并,结果存储到TR数组中;
            //注意:此时经过第一轮的归并排序的结果是存储到TR数组了;
            MergePass(L->r, TR, k, L->length);
            k = 2*k;
            //将刚刚归并排序后的TR数组,按照s = 2k的长度进行拆分合并. 结果存储到L->r数组中;
            //注意:因为上一轮的排序的结果是存储到TR数组,所以这次排序的数据应该是再次对TR数组排序;
            MergePass(TR, L->r, k, L->length);
            k = 2*k;
            
        }
    }
    

    七、快速排序算法

    快速排序(Quick Sort)基本思想:通过一趟排序将排序记录分割成独立的两部分;其中一部分记录的关键字均为另一部分记录的关键字小,则可分别对两部分记录进行排序,以达到整个排序有序的目的。

    ...... 就这样一步步得到第一个枢轴是50

    Partition 函数的思路:
    •选取第一个关键字作为枢轴;
    •只要(low< high) 就循环持续的将表的两端进行交替向中间扫描(两端交替循环)
    •while 遍历从low.high的高端位置开始找,找到比枢轴小的关键字(高位调整循环

    • 如没有找到,则修改范围。 将high 递减;
    • 如果找到进行交换到低端位置 swap(L,low.high);

    •while 遍历从low.high的低端位置开始找,找到比枢轴大的关键字(低位调整循环

    • 如果没有找到,则修改范围,将low 递增:
    • 如果找到进行交换到高端位置 swap(L,low.high);
    //13.快速排序-对顺序表L进行快速排序
    //③交换顺序表L中子表的记录,使枢轴记录到位,并返回其所在位置
    //此时在它之前(后)的记录均不大(小)于它
    int Partition(SqList *L,int low,int high){
        int pivotkey;
        //pivokey 保存子表中第1个记录作为枢轴记录;
        pivotkey = L->r[low];
        //① 从表的两端交替地向中间扫描;
        while (low < high) {
            //② 比较,从高位开始,找到比pivokey更小的值的下标位置;
            while (low < high &&  L->r[high] >= pivotkey)
                high--;
            //③ 将比枢轴值小的记录交换到低端;
            swap(L, low, high);
            //④ 比较,从低位开始,找到比pivokey更大的值的下标位置;
            while (low < high && L->r[low] <= pivotkey)
                low++;
            //⑤ 将比枢轴值大的记录交换到高端;
            swap(L, low, high);
        }
        
        //返回枢轴pivokey 所在位置;
        return low;
    }
    
    //② 对顺序表L的子序列L->r[low,high]做快速排序;
    void QSort(SqList *L,int low,int high){
        int pivot;
        
        if(low < high){
            //将L->r[low,high]一分为二,算出中枢轴值 pivot;
            pivot = Partition(L, low, high);
            printf("pivot = %d L->r[%d] = %d\n",pivot,pivot,L->r[pivot]);
            //对低子表递归排序;
            QSort(L, low, pivot-1);
            //对高子表递归排序
            QSort(L, pivot+1, high);
        }
    }
    
    //① 调用快速排序(为了保证一致的调用风格)
    void QucikSort(SqList *L){
        QSort(L, 1, L->length);
    }
    

    最好的情况:Partition 函数时间复杂度分析:
    T(n) ≤ 2T(n/2) + n, T(1) = 0;
    T (n) ≤ 2(2T( n/4) + n/2) + n = 4T(n/4)+2n;
    T (n) ≤ 4(2T(n/8) + n/4) + 2n = 8T (n/8) + 3n;
    ... ...
    T (n) ≤ nT(1) + log2n X n = O(nlogn);
    最好的情况下时间复杂度为:O(nlogn)

    最坏的情况:Partition 函数时间复杂度分析:
    排序的序列刚好是逆向和正序.那么递归树需要递归n-1次,且在第i次划分需要经过n-i次
    关键字比较才找到比第i个记录,也就枢轴的位置. 那么比较的次数:(n-1)+(n-2)+(n-3)..+1 = (n(n-1))/2:
    最终的最坏情况下的时间复杂度为 O(n2)

    快速排序优化
    //14 快速排序-优化
    int Partition2(SqList *L,int low,int high){
        int pivotkey;
        
        /**1.优化选择枢轴**/
        //① 计算数组中间的元素的下标值;
        int m = low + (high - low)/2;
        //② 将数组中的L->r[low] 是整个序列中左中右3个关键字的中间值;
        //交换左端与右端的数据,保证左端较小;[9,1,5,8,3,7,4,6,2]
        if(L->r[low]>L->r[high])
            swap(L, low, high);
        //交换中间与右端的数据,保证中间较小; [2,1,5,8,3,7,4,6,9];
        if(L->r[m]>L->r[high])
            swap(L, high, m);
        //交换中间与左端,保证左端较小;[2,1,5,8,3,7,4,6,9]
        if(L->r[m]>L->r[low])
            swap(L, m, low);
        //交换后的序列:3,1,5,8,2,7,4,6,9
        //此时low = 3; 那么此时一定比选择 9,2更合适;
        
        
        /**2.优化不必要的交换**/
        //pivokey 保存子表中第1个记录作为枢轴记录;
        pivotkey = L->r[low];
        //将枢轴关键字备份到L->r[0];
        L->r[0] = pivotkey;
        
        //① 从表的两端交替地向中间扫描;
        while (low < high) {
            //② 比较,从高位开始,找到比pivokey更小的值的下标位置;
            while (low < high &&  L->r[high] >= pivotkey)
                high--;
            
            //③ 将比枢轴值小的记录交换到低端;
            //swap(L, low, high);
            //③ 采用替换的方式将比枢轴值小的记录替换到低端
            L->r[low] = L->r[high];
            
            //④ 比较,从低位开始,找到比pivokey更大的值的下标位置;
            while (low < high && L->r[low] <= pivotkey)
                low++;
            
            //⑤ 将比枢轴值大的记录交换到高端;
            //swap(L, low, high);
            //⑤ 采样替换的方式将比枢轴值大的记录替换到高端
            L->r[high] = L->r[low];
        }
        //将枢轴数值替换会L->r[low]
        L->r[low] = L->r[0];
        
        //返回枢轴pivokey 所在位置;
        return low;
    }
    
    //② 对顺序表L的子序列L->r[low,high]做快速排序;
    #define MAX_LENGTH_INSERT_SORT 7 //数组长度的阀值
    void QSort2(SqList *L,int low,int high){
        int pivot;
        //if(low < high){
        //当high-low 大于常数阀值是用快速排序;
        if((high-low)>MAX_LENGTH_INSERT_SORT){
            //将L->r[low,high]一分为二,算出中枢轴值 pivot;
            pivot = Partition(L, low, high);
            printf("pivot = %d L->r[%d] = %d\n",pivot,pivot,L->r[pivot]);
            //对低子表递归排序;
            QSort(L, low, pivot-1);
            //对高子表递归排序
            QSort(L, pivot+1, high);
        }else{
            //当high-low小于常数阀值是用直接插入排序
            InsertSort(L);
        }
    }
    
    //① 快速排序优化
    void QuickSort2(SqList *L){
        QSort2(L,1,L->length);
    }
    

    测试代码

    #define N 9
    int main(int argc, const char * argv[]) {
        // insert code here...
        printf("Hello, 排序算法\n");
        
        int i;
        int d[N]={9,1,5,8,3,7,4,6,2};
        //int d[N]={9,8,7,6,5,4,3,2,1};
        //int d[N]={50,10,90,30,70,40,80,60,20};
        SqList l0,l1,l2,l3,l4,l5,l6,l7,l8,l9,l10;
       
        for(i=0;i<N;i++)
            l0.r[i+1]=d[i];
        
        l0.length=N;
        l1=l2=l3=l4=l5=l6=l7=l8=l9=l10=l0;
        
        printf("排序前:\n");
        print(l0);
        printf("\n");
        
        //1.初级冒泡排序
        printf("初级冒泡排序:\n");
        BubbleSort0(&l0);
        print(l0);
        printf("\n");
        
        //2.冒泡排序
        printf("冒泡排序:\n");
        BubbleSort(&l1);
        print(l1);
        printf("\n");
        
        //3.冒泡排序优化
        printf("冒泡排序(优化):\n");
        BubbleSort2(&l2);
        print(l2);
        printf("\n");
        
        //4.选择排序
        printf("选择排序:\n");
        SelectSort(&l3);
        print(l3);
        printf("\n");
        
        //5.直接插入排序
        printf("直接插入排序:\n");
        InsertSort(&l4);
        print(l4);
        printf("\n");
        
        //6.希尔排序
        printf("希尔排序:\n");
        shellSort(&l5);
        print(l5);
        printf("\n");
        
        //7.堆排序
        //注意:执行对排序时更换一下数据源. 这里我为什么要这组数据,原因是为了与下标个位数字讲解时进行区分;因为在这个算法讲解过程,出现了很多下标的相关计算.
        /* int d[N]={50,10,90,30,70,40,80,60,20}; */
        printf("堆排序:\n");
        HeapSort(&l6);
        print(l6);
        printf("\n");
        
        //8.归并排序(递归)
        printf("归并排序(递归):\n");
        MergeSort(&l7);
        print(l7);
        printf("\n");
        
        //9.归并排序(非递归)
        printf("归并排序(非递归):\n");
        MergeSort2(&l8);
        print(l8);
        printf("\n");
        
        //10.快速排序
        printf("快速排序:\n");
        QucikSort(&l9);
        print(l9);
        printf("\n");
        
        //11.快速排序-优化
        printf("快速排序(优化):\n");
        QuickSort2(&l10);
        print(l10);
        printf("\n");
        
        
        printf("\n");
        return 0;
    }
    

    相关文章

      网友评论

          本文标题:数据结构与算法(十六):排序算法

          本文链接:https://www.haomeiwen.com/subject/ohfwzdtx.html