【数据结构】—交换排序之快速排序究极详解,手把手带你从简单的冒泡排序升级到排序的难点{快速排序}(含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.xdnf.cn/news/139856.html

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

相关文章

Eureka服务器注册

一。Eureka服务器注册 1.pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://mav…

Android之MediaMetricsService实现本质(四十二)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药. 更多原创,欢迎关注:Android…

移动端H5封装一个 ScrollList 横向滚动列表组件,实现向左滑动

效果&#xff1a; 1.封装组件&#xff1a; <template><div class"scroll-list"><divclass"scroll-list-content":style"{ background, color, fontSize: size }"ref"scrollListContent"><div class"scroll…

Python150题day08

2.基础语法篇 2.1 if 条件句 ①单个条件分支 使用input函数接收用户的输入&#xff0c;如果用户输入的整数是偶数&#xff0c;则使用print函数输出"你输入的整数是:{value],它是偶数”&#xff0c;[value]部分要替换成用户的输入。 解答: value input("请输⼊⼀…

Jmeter——结合Allure展示测试报告

在平时用jmeter做测试时&#xff0c;生成报告的模板&#xff0c;不是特别好。大家应该也知道allure报告&#xff0c;页面美观。 先来看效果图&#xff0c;报告首页&#xff0c;如下所示&#xff1a; 报告详情信息&#xff0c;如下所示&#xff1a; 运行run.py文件&#xff0c;…

在React中,什么是组件的状态(state)?如何更新组件的状态?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 创建和初始化状态⭐ 更新状态⭐ 注意事项⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前…

【PyTorch攻略(2/7)】 加载数据集

一、说明 PyTorch提供了两个数据原语&#xff1a;torch.utils.data.DataLoader和torch.utils.data.Dataset&#xff0c;允许您使用预加载的数据集以及您自己的数据。数据集存储样本及其相应的标签&#xff0c;DataLoader 围绕数据集包装一个可迭代对象&#xff0c;以便轻松访问…

C++ 共享内存相关的API

C 共享内存相关的API 1.什么是共享内存1.共享内存的概念2.共享内存的原理3.共享内存使用注意点 2.共享内存有关API的操作函数及示例1.新建共享内存-shmget2.连接共享内存到当前的地址空间-shnat3.当前进程分离共享内存shmdt4.控制共享内存-shmctl5.共享内存操作示例 3.共享内存…

Node2Vec实战---《悲惨世界》人物图嵌入

1. pip各个包后导入 import networkx as nx # 图数据挖掘 import numpy as np # 数据分析 import random # 随机数# 数据可视化 import matplotlib.pyplot as plt %matplotlib inline plt.rcParams[font.sans-serif][SimHei] # 用来正常显示中文标签 plt.rcParams[axes.uni…

定制SQLmap和WAF绕过

1. SQLmap tamper 脚本编写 以sqli-lab第26关为例 输入?id1’ --&#xff0c;报错字符型注入 考虑闭合问题&#xff0c;输入?id1’ and 1&#xff0c;但是回显中and和空格消失了&#xff0c;可知and和空格被过滤了 因为and和or被过滤考虑使用双写绕过手段&#xff0c;空格使…

Linux常用命令—find命令大全

文章目录 一、find命令常用功能1、find命令的基本信息如下。2、按照文件名搜索3、按照文件大小搜索4、按照修改时间搜索5、按照权限搜索举例&#xff1a;6、按照所有者和所属组搜索7、按照文件类型搜索8、逻辑运算符 一、find命令常用功能 1、find命令的基本信息如下。 命令名…

java:java.util.MissingResourceException: Cant find bundle for base name解决方式

java&#xff1a;java.util.MissingResourceException: Cant find bundle for base name解决方式 1 前言 代码执行如下&#xff1a; ResourceBundle.getBundle("res.Message",Locale.getDefault(), ReadMyProps.class.getClassLoader());或 ResourceBundle.getBu…

(25)(25.1) 光学流量传感器的测试和设置

文章目录 25.1.1 测试传感器 25.1.2 校准传感器 25.1.3 测距传感器检查 25.1.4 预解锁检查 25.1.5 首次飞行 25.1.6 第二次飞行 25.1.7 正常操作设置 25.1.8 视频示例&#xff08;Copter-3.4&#xff09; 25.1.9 空中校准 25.1.1 测试传感器 将传感器连接至自动驾驶仪…

ROS2 从头开始:第 8 部分 - 使用 ROS2 生命周期节点简化机器人软件组件管理

一、说明 欢迎来到我在 ROS2 上的系列的第八部分。对于那些可能不熟悉该系列的人&#xff0c;我已经涵盖了一系列主题&#xff0c;包括 ROS2 简介、如何创建发布者和订阅者、自定义消息和服务创建、组合和执行器以及 DDS 和 QoS 配置。如果您还没有机会查看以前的帖子&#xff…

2023华为杯数学建模D题第三问——区域双碳目标情景设计样例

在第二问建立好预测模型的基础上&#xff0c;如何设计第三问所说的区域双碳路径&#xff0c;以对宏观政策进行指导&#xff01; 采用STIRPA的基本模型对中国碳达峰时间进行预测&#xff0c;对该模型公式两边取对数得到&#xff1a; 其中&#xff1a;P为人口&#xff0c;A为GDP…

异常记录-VS

1.文件加载失败 无法找到指定路径 Frame GUID: a6c744a8-0e4a-4fc6- 886a-064283054674 Frame mode: VSFM_ MdiChild Error code: 0x80131515 未理会这个提示&#xff0c;可以打开运行项目&#xff0c;只是会跳出这个弹窗。 无法关闭这个异常的窗口。

十四、MySql的用户管理

文章目录 一、用户管理二、用户&#xff08;一&#xff09;用户信息&#xff08;二&#xff09;创建用户1.语法&#xff1a;2.案例&#xff1a; &#xff08;三&#xff09; 删除用户1.语法&#xff1a;2.示例&#xff1a; &#xff08;四&#xff09;修改用户密码1.语法&#…

【MT7628AN】IOT | MT7628AN OpenWRT开发与学习

IOT | MT7628AN OpenWRT开发与学习 时间:2023-06-21 文章目录 `IOT` | `MT7628AN` `OpenWRT`[开发与学习](https://blog.csdn.net/I_feige/article/details/132911634?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22132911634…

【C语言】指针的进阶(四)—— 企业笔试题解析

笔试题1&#xff1a; int main() {int a[5] { 1, 2, 3, 4, 5 };int* ptr (int*)(&a 1);printf("%d,%d", *(a 1), *(ptr - 1));return 0; } 【答案】在x86环境下运行 【解析】 &a是取出整个数组的地址&#xff0c;&a就表示整个数组&#xff0c;因此…

vue项目打包优化

首先第一步通过浏览器看首次加载的问题大小&#xff0c;时间跨度等方面入手 1. Coverage观察 Coverage是chrome开发者工具的一个新功能&#xff0c;从字面意思上可以知道它是可以用来检测代码在网站运行时有哪些js和css是已经在运行&#xff0c;而哪些js和css是还没有用到的&a…