【数据结构】—交换排序之快速排序究极详解,手把手带你从简单的冒泡排序升级到排序的难点{快速排序}(含C语言实现)

                                       食用指南:本文在有C基础的情况下食用更佳 

                                       🔥这就不得不推荐此专栏了:C语言

                                       ♈️今日夜电波:靴の花火—ヨルシカ

                                                                0:28━━━━━━️💟──────── 5:03
                                                                    🔄   ◀️   ⏸   ▶️    ☰ 

                                      💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍 


目录

♉️一、前置知识—什么是交换排序

♊️二、冒泡排序

        冒泡排序的思想

        冒泡排序的实现

♋️三、快速排序 (排序算法中的重点,肥肠重要!!!)

        快速排序的思想

        快速排序的三种版本实现

        1、hoare版本(基础版本)

        ♒️快排的优化  

        2、 挖坑法

        3、前后指针法


♉️一、前置知识—什么是交换排序

        交换排序的基本思想是通过比较相邻元素的大小关系,如果两个相邻元素的大小关系不满足排序要求,就交换它们的位置,以达到排序的目的。交换排序分为两种,即冒泡排序和快速排序。

♊️二、冒泡排序

        冒泡排序的思想

        冒泡排序是一种基本的排序算法,它的思想是将待排序的元素依次比较相邻的两个元素,根据比较结果交换它们的位置,从而使较大(或较小)的元素逐渐往后移动,最终实现整个序列的排序。

        具体的实现过程如下:

        1. 从序列的第一个元素开始,依次比较相邻的两个元素的大小。

        2. 如果前一个元素比后一个元素大(或小),则交换它们的位置。

        3. 继续比较序列中下一个相邻的元素,直至最后一个元素。

        4. 重复以上操作,每一轮比较都将序列中最大(或最小)的元素排到了序列的末尾。

        5. 经过多轮比较后,序列中的元素就按照从小到大(或从大到小)的顺序排好了。

         一图理解~

         冒泡排序的实现

        太简单了,就不多解释了,看代码即可

void BubbleSort(int* a, int n)
{for (int j = 0; j < n; ++j){bool exchange = false;for (int i = 1; i < n-j; i++){if (a[i - 1] > a[i]){int tmp = a[i];a[i] = a[i - 1];a[i - 1] = tmp;exchange = true;}}if (exchange == false){break;}}
}

♋️三、快速排序 (排序算法中的重点,肥肠重要!!!)

        快速排序的思想

        快速排序的基本思想是选定一个轴值,将待排序序列划分为两部分,一部分是小于轴值的元素,一部分是大于轴值的元素。然后对于这两部分分别递归地进行快速排序,直到排序完成。具体实现时,可以选择待排序序列的第一个元素作为轴值(通常需要进行比较选出,后面会详讲,也可以随机选择一个元素作为轴值,然后将序列中的元素与轴值进行比较,小于轴值的元素放在轴值的左边,大于轴值的元素放在轴值的右边,相等的元素可以放在任 一 一边,最后将左右两部分序列递归地进行快速排序即可。快速排序一般有三种实现方法:hoare法、挖坑法、前后指针法

        一图先有个大概的了解~ 

        以下的内容是循序渐进的,建议大家一步一步往下看! 

        快速排序的三种版本实现

        1、hoare版本(基础版本)

        上来请先看一张图~

具体实现步骤如下:

  1. 选择一个基准元素 key,通常可以选择第一个或者最后一个元素作为基准元素(这里的key值选第一个)。

  2. 定义两个指针 left和 right 分别指向数组的开头和结尾。

  3. 从右向左扫描数组,如果当前元素大于或等于基准元素,则指针 right 向左移动一位。

  4. 从左向右扫描数组,如果当前元素小于或等于基准元素,则指针 left 向右移动一位。

  5. 如果 left < right,则交换指针所指向的元素。

  6. 当 left >= right 时,说明当前分区操作已经完成,交换基准元素和 left 指针指向的元素,并返回 left 的值。

  7. 对基准元素左侧的子数组和右侧的子数组分别递归执行上述步骤,直到数组长度为 1 或 0,此时数组就已经排好序了。

一些注意事项:

  1. Hoare 版本的快速排序可以避免对基准元素相同的元素的不必要交换操作,因此比 Lomuto 版本效率更高。

  2. 在实:现时需要注意边界条件的处理,尤其是数组下标越界问题。

        在这里大家可能会有一个疑惑,两个指针i,j相遇的位置的值,如何保证要比key的值小呢?

                答案:右边的值先走就能做到!!!

        当右侧的值先走时,如果它遇到了一个比基准元素小的值,那么它就会停下来,等待左侧的值去找到一个比基准元素大的值。接着左侧的值找到了一个比基准元素大的值后,交换左右值的位置,此时右侧的值就会从原来的位置继续走下去。由于左侧的值都比基准元素小,所以右侧的值再次遇到比基准元素小的值时,仍然会停下来等待左侧的值交换位置。这样,右侧的值先走就可以保证最后相遇的位置的值一定比基准元素的值小。

         在这里大家可能会有一个疑惑了,那我们能左边先走吗?

                当然可以,同样的道理,只需要key值在右边就行了!

         代码实现:

        详见代码内注释

void Swap(int* x, int* y)
{int tmp = *x;*x = *y;*y = tmp;
}int Partsort(int* a, int left, int right)
{int key = left;while (left < right){//找小,注意这里是右向左靠,在最后会的一步将是靠向左边while (left < right && a[right] >= a[key]){--right;}//找大while (left < right && a[left] <= a[key]){++left;}Swap(&a[left], &a[right]);//交换大于key和小于key位置的值}Swap(&a[key], &a[left]);//此时left==right,交换key和left的值return left;//用于递归下一步的key的左半边以及右半边}void Quicksort(int* a, int begin, int end)
{if (begin >= end)//当下标越界时,直接退出return;int key = Partsort(a, begin, end);// [begin, key-1] key [key+1, end] 大致形状Quicksort(a, begin, key - 1);//对左key半边进行排序Quicksort(a, key + 1, end);//对右半边进行排序
}

        ♒️快排的优化  

        先看个例子:(当我们的快排是排升序,然而我们要排的数据确是一个降序时)

         对此,快速排序有相应的优化:采用“三点取中法”,即:在待排序序列中随机选取三个元素,取中间值作为key值。这样做是为了避免选取到最大或最小值作为 key值,从而导致快排算法性能下降的问题。

        代码实现:

        详见代码内注释

void Swap(int* x, int* y)
{int tmp = *x;*x = *y;*y = tmp;
}int Getmid(int* a, int left, int right)//三点取中值
{int mid = (left + right) / 2;if (a[left] < a[mid]){if (a[mid] < a[right]){return mid;}else if (a[left] < a[right]){return right;}else{return left;}}else{if (a[mid] > a[right]){return mid;}else if (a[right] > a[left]){return left;}else{return right;}}
}int Partsort(int* a, int left, int right)
{int mid = Getmid(a, left, right);//三点取中值,优化Swap(&a[left], &a[mid]);//将中值放到最左边int key = left;while (left < right){//找小,注意这里是右向左靠,在最后会的一步将是靠向左边while (left < right && a[right] >= a[key]){--right;}//找大while (left < right && a[left] <= a[key]){++left;}Swap(&a[left], &a[right]);//交换大于key和小于key位置的值}Swap(&a[key], &a[left]);//此时left==right,交换key和left的值return left;//用于递归下一步的key的左半边以及右半边}void Quicksort(int* a, int begin, int end)
{if (begin >= end)//当下标越界时,直接退出return;int key = Partsort(a, begin, end);// [begin, key-1] key [key+1, end] 大致形状Quicksort(a, begin, key - 1);//对左key半边进行排序Quicksort(a, key + 1, end);//对右半边进行排序
}

        2、 挖坑法

        上来请先看一张图~

挖坑法的实现步骤如下:

  1. 设置左指针left和右指针right,以及基准元素key。

  2. 从右指针开始,向左遍历数组,直到找到第一个小于准元素key的元素,将其填入左指针所在位置的坑中,并将右指针指向左边一位。

  3. 从左指针开始,向右遍历数组,直到找到第一个大于准元素key的元素,将其填入右指针所在位置的坑中,并将左指针指向右边一位。

  4. 重复执行步骤2和步骤3,直到左指针等于右指针。

  5. 将基准元素填入最后的坑中。

  6. 对基准元素左边的子数组和右边的子数组,分别递归执行以上步骤,直到完成排序。

一些注意事项: 

  • 坑位可以是基准元素的位置,也可以是左指针或右指针的位置。

  • 在填坑的过程中,需要先将当前指针指向的元素保存起来,然后将其填入对应的坑中。

  • 在左指针等于右指针时,需要将基准元素填入最后的坑中。

代码实现:

        详见代码内注释

void Swap(int* x, int* y)
{int tmp = *x;*x = *y;*y = tmp;
}int Getmid(int* a, int left, int right)//三点取中值
{int mid = (left + right) / 2;if (a[left] < a[mid]){if (a[mid] < a[right]){return mid;}else if (a[left] < a[right]){return right;}else{return left;}}else{if (a[mid] > a[right]){return mid;}else if (a[right] > a[left]){return left;}else{return right;}}
}int Partsort2(int* a, int left, int right)
{int midi = Getmid(a, left, right);Swap(&a[left], &a[midi]);int key = a[left];// 保存key值以后,左边形成第一个坑int hole = left;while (left < right){// 右边先走,找小,填到左边的坑,右边形成新的坑位while (left < right && a[right] >= key){--right;}a[hole] = a[right];hole = right;// 左边再走,找大,填到右边的坑,左边形成新的坑位while (left < right && a[left] <= key){++left;}a[hole] = a[left];hole = left;}a[hole] = key;return hole;
}void Quicksort(int* a, int begin, int end)
{if (begin >= end)//当下标越界时,直接退出return;int key = Partsort2(a, begin, end);// [begin, key-1] key [key+1, end] 大致形状Quicksort(a, begin, key - 1);//对左key半边进行排序Quicksort(a, key + 1, end);//对右半边进行排序
}

        3、前后指针法

        上来请先看一张图~

前后指针法的实现步骤如下:

  1. 选取基准数。可以选择数组的第一个元素、最后一个元素、中间元素等。

  2. 设置两个指针,一个指向数组的第一个位置,另一个指向数组的最后一个位置。

  3. 从后往前遍历,找到第一个比基准数小的数,停止遍历。

  4. 从前往后遍历,找到第一个比基准数大的数,停止遍历。

  5. 交换两个数的位置。

  6. 重复步骤3到步骤5,直到前后指针相遇。

  7. 将基准数放到前后指针相遇的位置。

  8. 对基准数左右两边的子序列分别进行快速排序。

  9. 重复以上步骤,直到数组被完全排序。

 一些注意事项: 

         在交换两个数的位置时,如果前后指针指向的数相同,那么就不能进行交换操作,因为这样会导致两个数的值发生变化。同时,在递归的过程中,需要对子序列进行边界检查,避免出现数组越界的情况。

代码实现:

        详见代码内注释

void Swap(int* x, int* y)
{int tmp = *x;*x = *y;*y = tmp;
}int Getmid(int* a, int left, int right)//三点取中值
{int mid = (left + right) / 2;if (a[left] < a[mid]){if (a[mid] < a[right]){return mid;}else if (a[left] < a[right]){return right;}else{return left;}}else{if (a[mid] > a[right]){return mid;}else if (a[right] > a[left]){return left;}else{return right;}}
}// 前后指针
int Partsort3(int* a, int left, int right)
{int midi = Getmid(a, left, right);Swap(&a[left], &a[midi]);int prev = left;//前指针,从最左边开始int cur = prev + 1;//后指针int keyi = left;while (cur <= right){if (a[cur] < a[keyi] && ++prev != cur)//当后指针cur找到比key小的值时,先让前指针++prev让prev到交换位置(因为原来比cur小1个位置)再判断前指针是否更cur相等,如果相等了就没有必要再交换了{Swap(&a[prev], &a[cur]);}++cur;//继续遍历找比key值小的值}Swap(&a[prev], &a[keyi]);//最后交换prev与key的值return prev;//此时prev在key最终位置,后续用于递归
}void Quicksort(int* a, int begin, int end)
{if (begin >= end)//当下标越界时,直接退出return;int key = Partsort3(a, begin, end);// [begin, key-1] key [key+1, end] 大致形状Quicksort(a, begin, key - 1);//对左key半边进行排序Quicksort(a, key + 1, end);//对右半边进行排序
}


                    感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

                                       

                                                                         给个三连再走嘛~  

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

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

相关文章

时序预测 | MATLAB实现BO-BiGRU贝叶斯优化双向门控循环单元时间序列预测

时序预测 | MATLAB实现BO-BiGRU贝叶斯优化双向门控循环单元时间序列预测 目录 时序预测 | MATLAB实现BO-BiGRU贝叶斯优化双向门控循环单元时间序列预测效果一览基本介绍模型搭建程序设计参考资料 效果一览 基本介绍 MATLAB实现BO-BiGRU贝叶斯优化双向门控循环单元时间序列预测。…

C语言自定义类型详解(2)位断、枚举、联合知识汇总

本篇概要 本篇主要讲述C语言位断、枚举。联合的相关知识&#xff0c;包括哥哥自定义类型的基本声明&#xff0c;使用、优点。计算等相关知识。 文章目录 本篇概要1.位断1.1什么是位断&#xff1f;1.2 位段的内存分配1.3 位段的跨平台问题1.3 位段的应用 2.枚举2.1 枚举类型的声…

文件操作(2)

目录 文件操作的步骤&#xff1a; 流&#xff1a; 标准流&#xff1a; 文件指针&#xff1a; 文件信息区&#xff1a; 概念&#xff1a; 关系转化&#xff1a; 注意&#xff1a; 文件指针&#xff1a; 文件的打开和关闭&#xff1a; 打开方式&#xff1a; 打开成…

阿里云2核2G服务器e系列租用优惠价格182元性能测评

阿里云服务器经济型e实例2核2G配置优惠价格182.04元一年&#xff0c;系统盘ESSD Entry盘20GB起&#xff0c;公网带宽默认按使用流量&#xff0c;也可以选择按固定带宽计费&#xff0c;带宽值从1M到100M可选&#xff0c;阿腾云分享阿里云服务器2核2G优惠价格、详细配置及e系列CP…

(本地安装clickhouse)执行 nstall/doinst.sh时报错: cp: 无法创建普通文件“/usr/bin/clickho

问题描述 在本地安装clickhouse时&#xff0c; 解压&#xff1a;tar -zxvf clickhouse-common-static-21.9.4.35.tgz -C ../module/ 再进入cd clickhouse-common-static-21.9.4.35/ 执行&#xff1a;install/doinst.sh 报错 报错信息和截图&#xff1a; rootbigdata1 click…

CorelDraw是什么软件?好用吗

很多人都听过CorelDraw的名字&#xff0c;但不知道CorelDraw是什么样的软件。下面就让小编为大家详细介绍一下。 coreldraw是什么软件 CorelDraw是一款专业的图形设计软件。它的主要功能包括矢量图形和位图的编辑。用户可以利用其矢量图形编辑能力,设计各种图标、Logo等精细图…

数据结构与算法(C语言版)P2---线性表之顺序表

前景回顾 #mermaid-svg-sXTObkmwPR34tOT4 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-sXTObkmwPR34tOT4 .error-icon{fill:#552222;}#mermaid-svg-sXTObkmwPR34tOT4 .error-text{fill:#552222;stroke:#552222;}#…

C语言自定义类型详解(1)结构体知识汇总

本篇概要 本篇主要讲述C语言结构体的相关知识&#xff0c;包括结构体的基本声明&#xff0c;结构体的匿名结构&#xff0c;结构体的自引用&#xff0c;结构体变量的定义和初始化以及结构体的内存对齐等相关知识。 文章目录 本篇概要1.结构体1.1结构体的基本声明1.2结构体的特殊…

C# 查找迷宫路径

1.导入图像&#xff0c;并且将图像转灰度 using var img new Image<Bgr, byte>(_path); using var grayImg img.Convert<Gray, byte>(); 2.自动二值化图像 using var inputGrayOut new Image<Gray, byte>(grayImg.Size); // 计算OTSU阈值 var threshol…

C语言字符和字符串函数(2)

大家好&#xff0c;我今天继续来给大家分享C语言中的字符和字符串函数。 目录 ** 1.字符分类函数 2.memcpy 3.memmove 4.memcmp** 1.字符分类函数 islower判断字母小写函数 int main() {char ch w;if (islower(ch)){printf("小写\n");}else{printf("非小写\…

MQ - 26 基础功能:顺序消息和幂等的设计

文章目录 导图概述顺序消息的定义和实现消息队列的存储结构特性基于顺序存储结构的设计主流消息队列的实现机制幂等机制的定义和实现消息队列中幂等的定义生产幂等的设计实现1. 通过消息唯一 ID 实现幂等2. 通过生产者 ID 和自增序号实现幂等Kafka 的幂等机制的实现方案总结导图…

基于JAVA+SpringBoot+Vue+协同过滤算法+爬虫的前后端分离的租房系统

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 随着城市化进程的加快…

2023年研赛华为杯选题人数发布

选题人数发布&#xff01;经过长达30个小时&#xff0c;各个平台的相关选题投票、相关文章阅读量等各项数据进行统计&#xff0c;利用之前的评估办法&#xff08;详见注释&#xff09;。在开赛后30小时&#xff0c;我们基本确定各个赛题选题人数&#xff0c;大致为 题号选题人数…

Mac 上如何安装Mysql? 如何配置 Mysql?以及如何开启并使用MySQL

前言&#xff1a; 有许多开发的小伙伴&#xff0c;使用的是mac&#xff0c;那么在mac上如何安装&#xff0c;配置Mysql&#xff0c;以及使用Mysql了&#xff0c;今天来一个系统的教程。 安装Mysql 使用mysql前&#xff0c;我们需要先下载mysql&#xff0c;并按照以下几个步骤…

Redis延迟双删-架构案例2021(三十二)

数据库设计 某医药销售企业因业务发展&#xff0c;需要建立线上药品销售系统&#xff0c;为用户提供便捷的互联网药品销售服务、该系统除了常规药品展示、订单、用户交流与反馈功能外&#xff0c;还需要提供当前热销产品排名、评价分类管理等功能。 通过对需求的分析&#xf…

前端第二课,HTML,alt,title,width/heigh,border,<a>超链接,target,tr,td,th

目录 一、title: &#x1f49b; ​二、alt&#x1f499; 三、width/heigh&#x1f49c; 四、border ❤️ 五、超链接&#x1f49a; 六、target &#x1f497; 七、tr&#x1f495; 八、td&#x1f498; 九、th&#x1f49e; 十、rowspan 一、title: &#x1f49b; 快…

js-cookie使用 js深度克隆(判断引用类型是数组还是对象的方法)

cookie和深度拷贝的使用 1、js-cookie使用2、js深度克隆 1、js-cookie使用 前端的本地存储分为 localstorage、sesstionstorage、cookie 但是咱们有时候需要做7天免登录的需求时&#xff0c;选择 cookie 作为前端的本地存储是在合适不过的了 直接操作 cookie 可以&#xff0c; …

名义实际GDP-各地区-原始和结果(2000-2022年)

一、数据介绍 数据名称&#xff1a;名义、实际GDP-各地区-原始和结果 数据年份&#xff1a;2000-2022年 计算公式&#xff1a;实际GDP 名义GDP / GDP折算指数 数据基期&#xff1a;2000年 数据整理&#xff1a;自主整理 二、数据用途 数据用途 文献依据 经济发展水平 …

【笔记】ubuntu 20.04 + mongodb 4.4.14定时增量备份脚本

环境 ubuntu 20.04mongodb 4.4.14还没实际使用&#xff08;20230922&#xff09;后续到10月底如果有问题会修改 原理 只会在有新增数据时生成新的备份日期目录备份恢复时&#xff0c;如果恢复的数据库未删除&#xff0c;则会覆盖数据 准备 准备一个文件夹&#xff0c;用于…

Kuboard突然无法访问提示:Failed to connect to the database

一、背景 没有做任何特殊操作&#xff0c;突然kuboard访问时&#xff0c;提示如下信息&#xff1a; {"message": "Failed to connect to the database.","type": "Internal Server Error" }二、排查过程 此处kuboard为docker部署的…