排序算法解析:快排,归并 (全)

news2024/9/25 1:18:31

一、快排

原始快排

算法思想:
ps:排序的效果其实就是使一个数列中的每个数都满足左边数比它小、右边数比它大(假设升序)。

接下来我们来了解快排:

多次递归遍历,每单次遍历,设定一个限定值,单词遍历后,使限定值的左侧值均小于等于限定值,使限定值的右侧值均大于限定值 ,这样就使整体数列中的一个值处在其对应位置上。

之后以上一个限定值为边界,将其左右两侧的子数列分别进行上述单次遍历,最终使得整体数列中的每个值都达到其对应位置上,达到排序的效果。

实现思路:

单次遍历:设定两个指针left 和 right, 每次以数列组的第一个元素为限定值 。left (处于队首)负责找比限定值小的元素,right(处于队尾)负责找比限定值大的元素 。

第一步:left遇到比限定值小(或等于)的元素继续向前遍历(left++),遇到比限定值大的元素就停止遍历,

第二步:当left停止遍历后,right 进行遍历:遇到比限定值大的元素继续向前遍历(right--),遇到比限定值小(或等于)的元素就停止遍历,然后将left与right所指的值交换 。

第三步:重复上述一二步,直至left与right相遇 ,此时left所指元素小于等于限定值,然后将left所指值与限定值交换 ,至此单次遍历完成,限定值也到达其对应位置 。

多次递归遍历:设定两个变量L和R用来界定单次遍历的范围。(类似二叉树前序遍历)

第一次单次遍历范围(L,R);左子数列遍历范围:(L,left);右子数列遍历范围:(left+1,R)

动态图解析:
代码实现:
void Qsort(int* pq,int L ,int R)
{
    if (L>=(R-1))
        return;
    int base = L;               //初始限定值为数列的首元素
    int left = L,right = R-1;
    for (; left < right;)
    {       //左侧先遍历保证最后left指向值小于等于限定值
        for (; left!= right&&pq[left + 1] <= pq[base];)    //注意防止越界
        {
            left++;
        }
        for (; right!=left&&pq[right] > pq[base];)         //注意防止越界
        {
            right--;
        }
        if(right!=left)
        swap(&pq[left + 1], &pq[right]);
    }
    swap(&pq[left], &pq[base]);

    Qsort(pq,L, left);                   //遍历左侧子数列
    Qsort(pq,left+1,R);                  //遍历右侧子数列
    
}

挖坑法快排

算法思想:

挖坑法与原始快排的遍历顺序基本相同,但是挖坑法的逻辑思维相对更加直接 。

起始用gap记录限定值(gap即为坑),right先走遇到大于限定值继续遍历,遇到小于等于限定值的元素,将其放入坑中(gap),然后将right的位置记为坑,left重复right的遍历过程。

实现思路:

放入坑中(gap = right 或 gap = left ) 。其余基本和原始快排相同 。

动态图解析:
代码实现:
void GapQsort(int* pq, int L, int R)
{
    if (L >= (R))
        return;
    int gap = L;
    int left = L, right = R - 1;
    int base = pq[L];
    for (; left < right;)
    {
        for (; right!=left&& pq[right] > base; right--);
        pq[gap] = pq[right];
        gap = right;
        for (; left != right && pq[left] <= base; left++);
        pq[gap] = pq[left];
        gap = left;
    }
    pq[gap] = base;
    GapQsort(pq, L, gap);
    GapQsort(pq, gap + 1, R);
}

前后指针法快排

算法思想:

通过两个指针分别以不同的方式遍历数列,形成距离差和标志位,以节约时间复杂度。

快指针负责找大于限定值的元素,慢指针负责找小于等于限定值的元素。

实现思路:

单次遍历:定义前指针front 和后指针 last ,front正常向前遍历,front每遍历一个元素,last做一次检查:last所指元素是否小于等于限定值,若符合,则last也向前遍历;反之,last停止遍历,等待front向前遍历找到小于等于限定值的元素,然后将front与last指定元素交换 ,以此类推,直到front遍历完整个数列,然后将限定值交换至last的位置上 。

动态图解析:
代码实现:
void FRQsort(int* pq, int L, int R)
{
    if (L >= R)
        return;
    int front, last;
    front = last = L;
    int base = Getmid(pq,L,R-1);
    swap(&pq[base], &pq[L]);
    for (front++; front < R; front++)
    {
        if (pq[front] <= pq[L])
            swap(&pq[front], &pq[++last]);
    }
    swap(&pq[last], &pq[L]);
    FRQsort(pq, L, last);
    FRQsort(pq, last+1, R);

}    
int Getmid(int* pq, int left,int right)
{
    int mid = pq[(left+right) / 2];
    if (pq[mid] < pq[left])
    {
        if (pq[left] < pq[right])
            return left;
        else if (pq[mid] < pq[right])
        {
            return right;
        }
        else
            return mid;
    }
    else
    {
        if (pq[right] < pq[left])
            return left;
        else if (pq[mid] < pq[right])
            return mid;
        else
            return right;
    }
}

非递归实现快排

算法思想:

前后指针法多次排序的方式是通过将排序数列的范围作为函数参数来实现的,而非递归的实现中,单次排序的方式与前后指针法无异,区别在于多次排序:将排序数列的范围传入栈空间中,进而达到递归的效果。

例:假设待排序数列的元素如下(以下标形式): 0 1 2 3 4 5

第一次排序传入排序数列范围 (0,5), 假设单次排序后限定值下标为 2 ;

第二次排序传入排序数列范围 (0,1),(3,5)假设单次排序后限定值下标分别为 1 ,4

第三次排序传入排序数列范围 (0,0),(3,3),(5,5)

此时传入下标相等,说明只有一个元素,则排序无意义,即无需排序。

实现思路:

第一步将初始数列范围入栈,用一个变量basei表示限定值的下标,用left,right表示范围

第二步在不越界的情况下,将左右子数列分别入栈 。注:遍历子数列时要更新left,right

越界判定:
保持不越界且左子数列至少有两个元素 :basei >= left + 2
同理,右子数列防越界判定 :basei <= right - 3
代码实现:
void No_reQSort(int* pq,int k)
{
    Stack* newstack = (int*)malloc(sizeof(Stack));
    Stack_init(newstack);
    Stack_push(newstack, 0);
    Stack_push(newstack, k);

    for (int left, right; Stack_Empty(newstack);)
    {
        right = Stack_Top(newstack);
        Stack_pop(newstack);
        left = Stack_Top(newstack);
        Stack_pop(newstack);
        int basei = auxFRQsort(pq, left, right);
        if (basei >= left + 2)
        {
            Stack_push(newstack, left);
            Stack_push(newstack, basei);
        }
        if (basei <= right - 3)
        {
            Stack_push(newstack, basei + 1);
            Stack_push(newstack, k);
        }
    }
}

二、归并排序

递归实现归并

算法思想:

假设有两个数列a(2,1,3)和b(5,4)将这两个数列组合成一个有固定顺序的(升/降序)数列,这个过程即为两个数列的归并排序。

所以,若有一个待排序数列,我们可以将其分为多个子数列,并对每对子数列进行递归排序,最终使得整体数列有序。

归并排序包含三大部分:两个子数列排序,整体数列分层递归

两个子数列排序:

建立一块新空间(两个子数列合并大小)用两个指针分别遍历两个子数列 ,(假设升序)利用指针比较子数列的元素,元素小的数列将优先拷贝到新空间,直至其中一个子数列先被拷贝完,

再将剩于的一个数列的元素全部拷贝到新空间。

整体数列分层递归:

先将数列按照逐层分裂1/2的方式分解,再从最后一层开始排序并逐层归并。

实现思路:

两个函数的设置,子数列归并排序函数的参数要能表现两个子数列的范围,

整体递归函数的参数要能表示前1/2的数列范围和后1/2的数列范围。

下面代码用的是 (L1,R1) && (L2,R2)

动态解析:
代码实现:
void Tstrcpy(int* a, const int* b,int quant)
{
    for (int i = 0; i < quant; i++)
        a[i] = b[i];
}
void MergeSort(int* pq, int L1, int R1, int L2, int R2)
{
    int L1L = L1, R1R = R1;
    int L2L = L2, R2R = R2;
    int square = R2-L1+1;
    int* temp = (int*)malloc(sizeof(int) * square);
    int i = 0;
    for (i = 0; L1L <= R1R && L2L <= R2R;)
    {
        if (pq[L1L] > pq[L2L])
        {
            temp[i++] = pq[L2L++];
        }
        else
            temp[i++] = pq[L1L++];
    }
    int xtem = (L1L > R1R) ? L2L : L1L;
    for (int k = i; i < square; )
        temp[i++] = pq[xtem++];
    Tstrcpy(&pq[L1], temp, square);
}
void Merge(int *pq,int head,int tail)
{
    if (head >= tail)
        return;
    int L1 = head, R1 = (head + tail) / 2;
    int L2 = (head + tail) / 2 + 1, R2 = tail;
    Merge(pq, L1, R1);
    Merge(pq, L2, R2);
    MergeSort(pq,L1,R1,L2,R2);
}

非递归实现归并

算法思想:

通过下标数值计算的方式,将相邻数列逐次排序。

实现思路:

定义一个gap ,初始为1 ,为相邻子数列的大小 ,每遍历一边gap *= 2,

直至数组以最大二等分的形式进行最后一次归并排序。

其中下标数值的计算可能会出现越界的情况,所以需要针对具体情况进行约束。

代码实现:
void No_reMerge(int* pq, int head, int tail)
{
    int gap = 1;
    int L1, R1, L2, R2 = 0;
    for (; gap <= tail; gap *= 2)
    {
        for (int head = 0,R2 = 0;R2<tail-1;head=head+2*gap)
        {
            L1 = head, R1 = head + gap - 1;
            L2 = head + gap, R2 = head - 1+2*gap;
            if (R1 >= tail)
                R1 = tail-1;
            if (L2 >= tail)
                L2 = tail-1;
            if (R2 >= tail)
                R2 = tail-1;
            //printf("( %d,%d--%d,%d )--", L1, R1, L2, R2);
            MergeSort(pq, L1,R1,L2,R2);

        }
        //printf("\n");
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/176128.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

02 |「数据结构、逻辑结构、物理结构」基本概念简析

前言 前言&#xff1a;简析数据结构、逻辑结构、物理结构。 文章目录前言一、数据结构1. 简介2. 数据3. 结构4. 分析5. 分类1&#xff09;线性结构&#xff08;线性表&#xff09;2&#xff09;树结构3&#xff09;图结构二、逻辑结构与物理结构1. 为什么要有逻辑结构和物理结构…

SpringBoot+Vue--前端搭建-笔记1

前端搭建 首先安装node.js(百度) 官网下载地址&#xff1a;http://nodejs.cn/download 以前写的关于npm 后端了解的npm_biubiubiu0706的博客-CSDN博客 安装Node.js淘宝镜像加速器(cnpm) npm install cnpm -g(可以不安装) #建议使用如下语句解决npm速度慢的问题 好比设置仓…

代码随想录算法训练营三期 day 24 - 回溯 (1) (补)

回溯算法理论基础 什么是回溯法 回溯法也可以叫做回溯搜索法&#xff0c;它是一种搜索的方式。回溯是递归的副产品&#xff0c;只要有递归就会有回溯。所以以下讲解中&#xff0c;回溯函数也就是递归函数&#xff0c;指的都是一个函数。 回溯法的效率 回溯的本质是穷举&…

【手把手教你学51单片机】中断的优先级

注&#xff1a;本文章转载自《手把手教你学习51单片机》&#xff01;因转载需要原文链接&#xff0c;故无法选择转载&#xff01; 如若侵权&#xff0c;请联系我进行删除&#xff01;上传至网络博客目的为了记录自己学习的过程的同时&#xff0c;同时能够帮助其他一同学习的小伙…

第四十三章 动态规划——最长单调序列模型

第四十三章 动态规划——最长单调序列模型一、最长单调序列模型1、模型母题2、思路分析&#xff08;两种方法&#xff1a;DP&#xff0c;贪心&#xff09;二、模型的应用1、AcWing 1017. 怪盗基德的滑翔翼&#xff08;1&#xff09;问题&#xff08;2&#xff09;分析&#xff…

C规范编辑笔记(十四)

往期文章&#xff1a; C规范编辑笔记(一) C规范编辑笔记(二) C规范编辑笔记(三) C规范编辑笔记(四) C规范编辑笔记(五) C规范编辑笔记(六) C规范编辑笔记(七) C规范编辑笔记(八) C规范编辑笔记(九) C规则编辑笔记(十) C规范编辑笔记(十一) C规范编辑笔记(十二) C规范编辑笔记(…

Linux进程学习【一】

✨个人主页&#xff1a; Yohifo &#x1f389;所属专栏&#xff1a; Linux学习之旅 &#x1f38a;每篇一句&#xff1a; 图片来源 &#x1f383;操作环境&#xff1a; CentOS 7.6 阿里云远程服务器 Perseverance is not a long race; it is many short races one after another…

Linux基本功系列之rename命令实战

文章目录一. rename 命令介绍二. 语法格式及常用选项三. 参考案例3.1 将当前目录下所有.cfg的文件&#xff0c;替换为.txt结尾3.2 将所有出现mufeng的部分都替换为mufeng13.3 将mufeng0开头都变成mufeng00开头3.4 rename支持正则表示式总结前言&#x1f680;&#x1f680;&…

2023-1-22 刷题情况

积水面积 先祝大家新年快乐&#xff0c;新的一年&#xff0c;万事如意。 题目描述 一组正整数&#xff0c;分别表示由正方体叠起的柱子的高度。若某高度值为 xxx&#xff0c;表示由 xxx 个正立方的方块叠起&#xff08;如下图&#xff0c;0≤x≤50000 \le x \le 50000≤x≤5…

卷积神经网络进阶--基础知识

卷积神经网络进阶 b站课程链接碳基生物都能学会的神经网络&#xff08;跳着看的&#xff09; 因为我用的是pytorch&#xff0c;而该课程是用tenserflow的&#xff0c;所以主要记了一下理论 为什么要讲不同的网络结构 不同的网络结构解决的问题不同不同的网络结构使用的技巧不同…

【人工智能原理自学】卷积神经网络:打破图像识别的瓶颈

&#x1f60a;你好&#xff0c;我是小航&#xff0c;一个正在变秃、变强的文艺倾年。 &#x1f514;本文讲解卷积神经网络&#xff1a;打破图像识别的瓶颈&#xff0c;一起卷起来叭&#xff01; 目录一、手写体识别二、“炼丹”一、手写体识别 在机器学习、神经网络领域&#…

【数据分析】(task4)数据可视化

note matplotlib的四个容器&#xff1a; Figure&#xff1a;顶层级&#xff0c;用来容纳子 Axes&#xff0c;一组具体绘图元素和画布&#xff08;canvas&#xff09;。 画板。Axes&#xff1a;matplotlib宇宙的核心&#xff0c;容纳了大量元素用来构造一幅幅子图&#xff0c;一…

【QT5.9】与MFC对比学习笔记-感悟篇【2023.01.22】

简介 在公司从事MFC的程序维护一年两个月&#xff0c;期间因为公司被QT告侵权对QT产生了抵触的心情。现在无奈要用到&#xff0c;需要抓紧学习了。 正文 1.数据模型 先说下刚用到的模型&#xff0c;模型也叫数据模型&#xff0c;也就是耳熟的MVC架构中的M&#xff08;Model…

我用笨办法啃下了一个开源项目的源码!

目录 1、从最简单的源码开始&#xff1a;别幻想一步登天 2、循序渐进&#xff1a;先搞定底层依赖的技术 3、一定要以Hello World作为入口来阅读 4、抓大放小&#xff0c;边写注释边画图 5、反复三遍&#xff0c;真正理解源码 6、借力打力&#xff0c;参考源码分析书籍及博客 7…

研一寒假C++复习笔记--引用的使用

​​​​​​​ 目录 1--引用的基本语法 2--引用的注意事项 3--在函数参数中使用引用 4--引用作函数的返回值 5--引用的本质 6--常量引用 1--引用的基本语法 引用相当于给变量起别名&#xff0c;其基本语法如下&#xff1a; 数据类型 &别名 原名 # include <…

Linux操作系统之进程信号

代码存放在&#xff1a;https://github.com/sjmshsh/System-Call-Learn/tree/master/signal 我们先来看一张图&#xff0c;了解一下通过阅读本博客&#xff0c;你可以收获什么。 背景知识 首先我说明一点 信号 ! 信号量 我们这篇文章讲解的是信号&#xff0c;不是信号量 信…

POJ3263. Tallest Cow题解(c++ 前缀和)

POJ3263. Tallest Cow 传送门&#xff1a;Tallest Cow 题目&#xff1a; 有N头牛站成一行。两头作能够相支看见&#xff0c;当且仅当它们中间的牛身高都比它们矮。现在&#xff0c;我们只知道其中最高的牛是第P头&#xff0c;它的身高是H&#xff0c;不知道剩余N-1头牛的身高。…

大数据之Kafka高级知识点

文章目录前言一、分片和副本机制&#xff08;一&#xff09;分片机制&#xff08;二&#xff09;副本二、Kafka如何保证数据不丢失&#xff08;一&#xff09;Producer生产者&#xff08;二&#xff09;Broker&#xff08;三&#xff09;Consumer消费者三、消息存储和查询机制总…

重新设计 TCP 协议

看一段关于 TCP 协议的历史讨论&#xff0c;源自&#xff1a;The design philosophy of the DARPA internet protocols 读这段文字时&#xff0c;你可能觉得这不是在谈 TCP&#xff0c;而是在创造一个新协议&#xff0c;但事实上这就是 TCP 在被创造过程中真实的纠结。 现在来…

Java知识点细节简易汇总——(6)面向对象编程(中级部分)

一、IDE快捷键 删除当前行, 默认是 ctrl Y 自己配置 ctrl d复制当前行, 自己配置 ctrl alt 向下光标补全代码 alt /添加注释和取消注释 ctrl / 【第一次是添加注释&#xff0c;第二次是取消注释】导入该行需要的类 先配置 auto import , 然后使用 altenter 即可快速格式化…