23考研王道排序(第八章)自用笔记

排序

推荐可视化工具

  1. Data Structure Visualizations
  2. visualgo.net

排序的基本概念

相关概念

排序(sort) 就是重新排列表中的元素,使表中的元素满足按关键字有序的过程。
输入∶ n个记录R1, R2…Rn,对应的关键字为k1, k2… ., kn
输出︰ 输入序列的一个重排R1’, R2’…Rn’,使得有k1’≤k2’≤…≤kn’(也可递减)

排序算法的评价指标

  1. 时间复杂度
  2. 空间复杂度
  3. 稳定性(稳定的排序算法不一定比不稳定的好,根据需求而定)
    在这里插入图片描述

排序算法的分类

  1. 内部排序——数据都在内存中(关注如何使算法时、空复杂度更低)
  2. 外部排序——数据太多,无法全部放入内存(还要关注如何使读/写磁盘次数更少)

插入排序

直接插入排序和带哨兵插入排序

算法思想: 每次将一个待排序的记录按其关键字大小插入到前面已排好序的子序列中,直到全部记录插入完成。
在这里插入图片描述
不带哨兵的数组存储形式
在这里插入图片描述
带哨兵的数组存储形式
在这里插入图片描述

void InsertSort(int a[], int n) {     //不带哨兵 ,n为数组大小
    int i, j, temp;
    for (i = 1; i < n; i++) {
        if (a[i] < a[i - 1]) {      //i指向待排序元素
            temp = a[i];            //temp为待排序元素
            for (j = i - 1; j >= 0 && a[j] > temp; j--) {   //和前面的进行比较 ,如果前面的比此时的大
                a[j + 1] = a[j];       //大于temp的元素后移,后面的元素a[j+1]依次被前面的元素a[j]覆盖
            }
            a[j + 1] = temp;    //最后空位赋值temp
        }
    }
}
//哨兵位于a[0],数据存储在a[0]~a[n]
void InsertSort(int a[], int n) {     //带哨兵,n为数组存储的元素的个数
    int i, j;
    for (i = 2; i <= n; i++) {      //依次将a[2]~a[n]插入到前面已排序序列
        if (a[i] < a[i - 1]) {      //若a[i]关键码小于其前驱,将A[i]插入有序表
            a[0] = a[i];            //复制为哨兵,A[0]不存放元素
            for (j = i - 1; a[0] < a[j]; j--) {  //从后往前查找行
                a[j + 1] = a[j];       //向后挪位
            }
            a[j + 1] = a[0];    //复制到插入位置
        }
    }
}

算法效率分析
最坏时间复杂度:——O(n2)
最好时间复杂度——O(n)
算法稳定性︰稳定

折半插入排序

折半插入排序的数组存储形式
在这里插入图片描述

具体操作:
①在将一个新元素插入已排好序的数组的过程中,寻找插入点时,将待插入区域的首元素设置为a[low],末元素设置为a[high]。
②将待插入元素与a [mid] (其中mid=(low+high)/2)相比较,如果比参考元素小,则选择a[low]到a[mid-1]为新的插入区域(即high=mid-1),否则选择a[mid+1]到a[high]为新的插入区域(即low=mid+1)
③如此直至low<=high不成立,即将high+1位置及之后所有元素后移一位,并将新元素插入a[high+1]。

void InsertionSort(int a[], int n) {	//n为数组存储的元素的个数
    int i, j, low, high, mid;
    for (i = 2; i <= n; i++)  //依次将a[2]~a[n]插入到前面已经排好序的列表
    {
        a[0] = a[i];  //将a[i]暂存到a[0]
        low = 1;
        high = i - 1;
        while (low <= high) {
            mid = (low + high) / 2;  //取中间位置(利用low + (high - low) / 2求mid是为了防止整数溢出问题)
            if (a[mid] > a[0])  //查找左子表
            {
                high = mid - 1;
            } else  //查找右子表
            {
                low = mid + 1;
            }
        }
        for (j = i - 1; j >= high + 1; --j)  //i - 1指待插入元素的前一个元素,即有序列表中所有大于待插入元素的最后一个元素;high + 1指有序列表中所有大于待插入元素的第一个元素
        {
            a[j + 1] = a[j];  //统一后移元素
        }
        a[high + 1] = a[0];  //插入操作
    }
}

希尔排序(Shell Sort)

希尔排序的数组存储形式
在这里插入图片描述

希尔排序︰先将待排序表分割成若干形如 L[i,i + d, i + 2. …, i + kd]的“特殊”子表,对各个子表分别进行直接插入排序。缩小增量d,重复上述过程,直到d=1为止。

  1. 希尔排序︰先追求表中元素部分有序,再逐渐逼近全局有序

  2. 步骤 定义数组a[9],a[0]不存储:①确定增量d(d的取值不唯一,一般是4,2,1)
    ②d=4时,可将数组分为(a[1],a[5]) (a[2],a[6]) (a[3],a[7]) (a[4],a[8])四组,每组直接插入排序后重新分组
    ③d=2时,经过d=4的分组排序后,又分成(a[1],a[3],a[5],a[7])(a[2],a[4],a[6],a[8])两组,每组进行直接插入排序后重新分组
    ④d=1时,直接a[9]数组直接插入排序
    在这里插入图片描述

  3. 代码

void ShellSort(int a[], int n) {		//n为数组存储的元素的个数
    int d, i, j;
    //A[0]只是暂存单元,不是哨兵,当j<=0时,插入位置已到
    for (d = n / 2; d >= 1; d /= 2) {
        for (i = d + 1; i <= n; i++) {
            if (a[i] < a[i - d]) {    //需将A[i]插入有序增量子表
                a[0] = a[i];  //暂存在A[0]
                for (j = i - d; j > 0 && a[0] < a[j]; j -= d) {     //记录后移,查找插入的位置
                    a[j + d] = a[j];    //插入
                }
                a[j + d] = a[0];
            }
        }
    }
}

空间复杂度: O(1)
时间复杂度 ∶和增量序列d1, d2, d3…的选择有关,目前无法用数学手段证明确切的时间复杂度最坏时间复杂度为O(n2),当n在某个范围内时,可达O(n1.3)
稳定性: 不稳定!
适用性: 仅适用于顺序表,不适用于链表

交换排序

交换排序的数组存储形式在这里插入图片描述

冒泡排序

从后往前(或从前往后)两两比较相邻元素的值,若为逆序〈即A[i-1]>A[i]),则交换它们,直到序列比较完。称这样过程为“—趟”冒泡排序。总共需进行n-1趟冒泡。
在这里插入图片描述

void BubbleSort(int a[], int n) {     //n为数组大小
    for (int i = 0; i < n - 1; i++) {
        for (int j = 1; j < n - i; j++) {
            if (a[j] < a[j - 1]) {
                int temp = a[j - 1];
                a[j - 1] = a[j];
                a[j] = temp;
            }
        }
    }
}

空间复杂度:O(1)
最好时间复杂度=O(n)
最坏时间复杂度=O(n2)
平均时间复杂度=O(n2)
稳定性:稳定
适用性:顺序表、链表都可以

快速排序

  1. 基本思想
    通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都小于或者等于另外一部分的所有数据,然后再按照此方法对这两部分数据分别进行快速排序,整个排序的过程可以递归进行,以此达到整个数据变成有序序列。

  2. 步骤
    ①设置两个变量low=0,high=n-1;
    ②设置中枢轴pivot=a[0](推荐),也可以随机数组中的元素
    ③从high开始向前搜索,即由后开始向前搜索(high- -),找到第一个小于pivot的值a[high],将A[high]和A[low]的值交换;
    ④从low开始向后搜索,即由前开始向后搜索(low++),找到第一个大于pivot的值a[low],将a[low]和a[high]的值交换;
    ⑤重复第③、④步,直到low==high;

  3. 图解
    快速排序

  4. 代码

//快速排序
int Partition(int a[], int high, int low) {
    int pivot = a[low];     //第一个元素作为中枢轴
    while (low < high) {    //用low、high搜索枢轴的最终位置
        while (low < high && a[high] >= pivot) {
            high--;
        }
        a[low] = a[high];   //比枢轴小的元素移动到左端
        while (low < high && a[low] <= pivot) {
            low++;
        }
        a[high] = a[low];   //比枢轴大的元素移动到右端
    }
    a[low] = pivot;     //枢轴元素存放到最终位置
    return low; //返回存放枢轴的最终位置
}

void QuickSort(int a[], int low, int high) {
    if (low < high) {   //递归跳出的条件
        int pivotpos = Partition(a, low, high);     //划分,其值代表一趟划分后的位置
        QuickSort(a, low, pivotpos - 1);       //划分左子表
        QuickSort(a, pivotpos + 1, high);       //划分右子表
    }
}

在这里插入图片描述

简单选择排序

简单选择排序的数组存储形式在这里插入图片描述

  1. 思想:每一趟在待排序元素中选取关键字最小的元素加入有序子序列

  2. 图解
    简单选择排序

  3. 代码

void SelectSort(int a[], int n) {
    int i, j;
    for (i = 0; i < n; i++) {
        int min = i;        //记录最小元素位置
        for (j = i + 1; j < n; j++) {   //在a[i]后寻找比a[i]小的元素
            if (a[j] < a[min]) {
                min = j;            //记录下标值
            }
        }
        if (min != i) {         //交换下标min和i的值
            int temp = a[i];
            a[i] = a[min];
            a[min] = temp;
        }
    }
}

在这里插入图片描述

堆排序

什么是堆

若n个关键字序列L[ 1…n]满足下面某一条性质,则称为堆((Heap) :
①若满足∶L(i)≥L(2i)且L(i)≥L(2i+1) (1 ≤ i ≤n/2)——大根堆(大顶堆)根结点数值最大
②若满足∶L(i)≤L(2i)且L(i)≤L(2i+1) (1 ≤i ≤n/2 )——小根堆(小顶堆)根结点数值最小
在这里插入图片描述
在这里插入图片描述

如何建立大小根堆(大根堆为例)

  1. 起初根据数组建立对应的二叉树

  2. 把所有非终端结点(有分支的结点) 都检查一遍,是否满足大根堆的要求,如果不满足,则进行调整(a[1 2 ……n/2])。n为结点个数
    ①检查当前结点是否满足根≥左、右,若不满足,将当前结点与更大的一个孩子互换
    注:i的左孩子——2i,i的右孩子—2i+1,i的父节点——i/2
    ②注意下坠,重新调整位置

  3. 代码:

//建立大根堆
void BuildMaxHeap(int a[], int len) {
    for (int i = len / 2; i > 0; i--) {
        HeadAdjust(a, i, len);
    }
}

//将以k为根的子树调整为大根堆
void HeadAdjust(int a[], int k, int len) {	//len为结点的个数
    a[0] = a[k];  //a[0]暂存根结点的值
    for (int i = 2 * k; i < len; i *= 2) {  //沿key较大的子结点向下筛选
        if (i < len && a[i] < a[i + 1]) {     //判断k为根结点的左右孩子谁更大
            i++;                   // 右孩子大则i+1
        }
        if (a[0] > a[i]) {      //判断此时k的值和最大的孩子结点谁更大
            break;          //满足大根堆,break
        } else {
            a[k] = a[i];      //不满足,a[i]调整到双亲结点
            k = i;            //修改k的值,一遍继续筛选
        }
    }
    a[k] = a[0];      //被筛选的结点放入最终位置
}

如何排序

①每一趟将堆顶元素加入有序子序列(与待排序序列中的最后一个元素交换)
②大根堆中删除堆顶元素,重新建立大根堆
③反复①③操作

void HeapSort(int a[], int len) {
    BuildMaxHeap(a, len);    //第一次建堆
    for (int i = len; i > 1; i--) {     //n-1趟交换和建堆
        Swap(a[i], a[1]); //堆顶元素和堆底元素交换
        HeadAdjust(a, 1, i - 1);  //把剩余的待排序元素整理成堆
    }
}

在这里插入图片描述

在堆中插入\删除新元素(代码可以写不重要)

对于根堆,新元素放到表尾,与父节点对比,若新元素比父节点更大,则将二者互换。新元素就这样一路“上升”,直到无法继续上升为止

被删除的元素用堆底元素替代,然后让该元素不断“下坠”,直到无法下坠为止

注意:对比次数的计算

归并排序

在这里插入图片描述

在这里插入图片描述
代码:

void Merge(int a[], int low, int mid, int high) {	//high为最后一个元素的下标
    int b[high + 1], i, j, k;      //b数组用于复制a数组
    for (i = low; i <= high; i++) { //a复制到b中
        b[i] = a[i];
    }
    for (i = low, j = mid + 1, k = i; i <= mid && j <= high; k++) {
        if (b[i] <= b[j]) {     //将小的放入a中
            a[k] = b[i++];
        } else {
            a[k] = b[j++];
        }
    }
    while (i <= mid) {
        a[k++] = b[i++];
    }
    while (j <= high) {
        a[k++] = b[j++];
    }
}

void MergeSort(int a[], int low, int high) {
    if (low < high) {
        int mid = (low + high) / 2;
        MergeSort(a, low, mid);
        MergeSort(a, mid + 1, high);
        Merge(a, low, mid, high);
    }
}

基数排序(要求仅手动模拟)


在这里插入图片描述
②第一趟看个位,个位是数字几,就把该数值放到对应的队列中
在这里插入图片描述
在这里插入图片描述
③第二趟看十位,十位是数字几,就把该数值放到对应的队列中
在这里插入图片描述
在这里插入图片描述
④第三趟看百位,百位是数字几,就把该数值放到对应的队列中
在这里插入图片描述
在这里插入图片描述
假设长度为n的线性表中每个结点aj的关键字由d元组(kjd-1, kjd-2,kjd-3,. . .,kj1 , kj0)组成
其中,0<=kji<=r-1(0<=j<n,0<=i<=d-1) ,r称为“基数

基数排序得到递减序列的过程如下,
1.初始化:设置r个空队列,Qr-1,Qr-2,…,Qo
2. 按照各个关键字位权重递增的次序(个、十、百),对d个关键字位分别做“分配”和“收集”
3. 分配:顺序扫描各个元素, 若当前处理的关键字位=x,则将元素插入Qx队尾
4. 收集:把Qr-1,Qr-2,…,Qo各个队列中的结点依次出队并链接

基数排序得到递增序列的过程如下,
1.初始化:设置r个空队列,Q0,Q1,……,Qr-1
5. 按照各个关键字位权重递增的次序(个、十、百),对d个关键字位分别做“分配”和“收集”
6. 分配:顺序扫描各个元素, 若当前处理的关键字位=x,则将元素插入Qx队尾
7. 收集:把Q0,Q1,……,Qr-1各个队列中的结点依次出队并链接

在这里插入图片描述

外部排序

外部排序:数据元素太多,无法一次全部读入内存进行排序
使用归并排序的方法,最少只需在内存中分配3块大小的缓冲区即可对任意一个大文件进行排序,外存中分成小块进入内存块中排序后在放入外存中。
在这里插入图片描述

“归并排序”要求各个子序列有序,每次读入两个块的内容,进行内部排序后写回磁盘

败者树

在这里插入图片描述
在这里插入图片描述
假设天津饭由派大星替代,并不会重新比拼,只会进行三次比拼,减少了对比次数
在这里插入图片描述
经过一次对比后的图:
在这里插入图片描述
在这里插入图片描述

置换选择排序

在这里插入图片描述
在这里插入图片描述

最佳归并树

相当于哈夫曼树的构造,小到大排序,依次排序,然后构造
例如构造1 2 2 5 6的最佳归并树
在这里插入图片描述

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值