数据结构之快速排序算法(快排)【图文详解】

P. S.:以下代码均在VS2019环境下测试,不代表所有编译器均可通过。
P. S.:测试代码均未展示头文件stdio.h的声明,使用时请自行添加。

  

在这里插入图片描述

                                           博主主页:LiUEEEEE
                                              C语言专栏
                                            数据结构专栏
                                         力扣牛客经典题目专栏

目录

  • 1、快排的基本思想
  • 2、快排的四种实现方法
  • 3、Hoare快速排序
  • 4、挖坑法快速排序
  • 5、前后指针法快速排序
  • 6、非递归法快速排序
  • 7、快速排序的优化
    • 7.1 三数取中
    • 7.2 少量数据另谋他法
  • 8、快速排序的特性总结
  • 9、结语

1、快排的基本思想


  快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为

  任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。




2、快排的四种实现方法


  快速排序有多种实现方法,其具体体现如下:
  • Hoare快速排序
  • 挖坑法快速排序
  • 前后指针法快速排序
  • 非递归法快速排序

下文将对上述四种实现方法依次进行讲解。




3、Hoare快速排序


  Hoare快速排序的示意图如下所示:

在这里插入图片描述
  其主要思想为:使用循环遍历数组

  • 将所给数组的第一个值的下标给予key变量和begin变量(下文中统一为keyi,方便理解其为下标地址而非数组成员值),将数组最后一个值的下标给予end变量。
  • 开始执行是令end向数组左侧遍历,寻找数值比数组下标为keyi的值小的数,若未寻找到,则继续向左侧遍历,若寻找到,则停止遍历,转而为begin向数组右侧遍历。
  • begin向数组右侧遍历,寻找数值比下标为keyi的值大的数,若寻找到,则交换数组中下标为end和begin的值。
  • 若在begin与end遍历过程中两变量出现相等的情况,则退出循环,将数组中下标为keyi和end的值交换。
  • 记录下交换位置,令其为新的分割点,利用递归的思想,将数组根据新分割点分割的左右两次子数组进行递归,直到出现新数组中L >= R。
  • 递归结束。


      其递归思想的图示如下,示例:成员为4,2,6,5,7,1,3,8,10,9的数组。
    在这里插入图片描述  当记录交换位置为3,即会有新的子数组【0,2】【4,9】,使用新数组进行递归。

在这里插入图片描述
  递归方法如上图所示(除3外其余递归交换位置为虚构,需实际计算得到)。

  • Hoare快速排序代码如下
void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}int PartSort1(int* a, int left, int right)
{if (left >= right)return;int begin = left;int end = right;int keyi = left;while (begin < end){while(begin < end && a[end] >= a[keyi]){end--;}while (begin < end && a[begin] <= a[keyi]){begin++;}Swap(&a[end], &a[begin]);}Swap(&a[end], &a[keyi]);keyi = begin;PartSort1(a, left, keyi - 1);PartSort1(a, keyi + 1, right);
}




4、挖坑法快速排序


  挖坑法快速排序示意图如下所示:

在这里插入图片描述
  其主要思想为:使用循环遍历数组

  • 将数组中keyi所在下标的值暂存起来,其位置看作坑位,先令end从右向左遍历寻找比暂存值小的值,寻找到后,将end所指向的值放入坑中,而后end所指向的位置变成新坑。
  • 再令begin从右向左寻找比暂存值大的值,寻找到后,将begin所指向的值放入坑中,而后begin所指向位置变为新坑。
  • 在循环过程中若begin与end相遇,则将暂存之放入此时的坑中。
  • 记录下最后一个坑的位置视作分界点,从分界点处分出两个新的子数组,进行下一轮递归,直至所分出的新数组L >= R。
  • 递归结束。
      其递归示意图与标题三中所展示一致,故不再做重复展示。

  • 挖坑法快速排序代码如下所示:
int PartSort2(int* a, int left, int right)
{if (left >= right)return;int key = a[left];int keyi = left;int begin = left;int end = right;while (begin < end){while (begin < end && a[end] >= key){end--;}a[keyi] = a[end];keyi = end;while (begin < end && a[begin] <= key){begin++;}a[keyi] = a[begin];keyi = begin;}a[keyi] = key;PartSort2(a, left, end - 1);PartSort2(a, end + 1, right);
}




5、前后指针法快速排序


  前后指针法快速排序示意图如下所示:

在这里插入图片描述
  其主要思想为:

  • 定义指针prev指向数组开始位置,cur指向数组开始第二个位置,并使用keyi记录数组中用作比较的数。

  • 令cur向数组右端遍历,若出现小于keyi的数则停止遍历,转为prev进行遍历。

  • 令prev向数组右端遍历,若出现大于keyi的数,则令其与cur交换位置,而后继续进行cur的遍历,直至cur达到数组末尾的下一位(即越界)。

  • 当cur越界后将当前prev所代表的值与key所代表值进行交换,并将prev所处位置视为分界点,从分界点处分出两个新的子数组进行递归,直至所递归的数组L >= R。

  • 递归结束。
      其递归示意图与标题三中所展示一致,故不再做重复展示。

  • 前后指针法快速排序代码如下所示:

void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}int PartSort3(int* a, int left, int right)
{if (left >= right)return;int prev = left;int cur = prev + 1;int key = left;while (cur <= right){if (a[cur] < a[key] && a[++prev] != a[key])Swap(&a[cur], &a[prev]);cur++;}Swap(&a[key], &a[prev]);PartSort3(a, left, prev - 1);PartSort3(a, prev + 1, right);
}




6、非递归法快速排序


  非递归的方法实现快速排序,其原理是使用栈来模拟代码在系统中的栈中的递归过程,也可称之为伪递归。
  有关栈的相关内容可在下方查看:

                                栈(使用顺序表构建)


  其主要思想为:
  • 使用栈来模拟系统中栈的功能,将数组的右左下标依次传入栈中,每一次取其栈顶位置数据分别作为单词快排的目标数组左下标和右下表(每一次取值后需删除栈顶元素)。

  • 在单次快排完成后判断其在分段点所分两个新的子数组左右下标是否合理(是否存在左下标大于或等于右下标的行为),若不存在则将右子数组右左下标依次推入栈中,而后将左子数组右左下标依次推入栈中,进行下一轮循环。

  • 其循环判断条件为栈中是否为空。

  • 有关其入栈顺序可不做过多研究,但要保证的前提是所取出数值需与操作数组的左右下标相对应,以避免不必要麻烦。

  • 非递归法快速排序代码如下所示:

void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}void QuickSortNonR(int* a, int left, int right)
{ST s;STInit(&s);STPush(&s, right);STPush(&s, left);while (!STEmpty(&s)){int begin = STTop(&s);STPop(&s);int end = STTop(&s);STPop(&s);int prev = begin;int cur = prev + 1;int key = begin;while (cur <= end){if (a[cur] < a[key] && a[++prev] != a[key])Swap(&a[cur], &a[prev]);cur++;}Swap(&a[key], &a[prev]);if (prev + 1 < end){STPush(&s, end);STPush(&s, prev + 1);}if (begin < prev - 1){STPush(&s, prev - 1);STPush(&s, begin);}}STDestroy(&s);
}




7、快速排序的优化


   此优化可适用于任意方法的可快速排序。
  在日常生活中,我们所使用排序功能的目标对象可能不会想日常练习中那样少,或许是一个非常庞大的数量,且其所提供的数据可能在原本基础上就有部分有序化,例如所给的大量数据中第一位为最小值或最大值,而这样的数据我们从一开始就进行快速排序的话,会让end从末尾一直遍历到数据首部或begin从数据首部一直遍历到末尾,拥有时间复杂度上的浪费,所以我们提出了一个 三数取中的方法来进行优化,且针对于大量数据,当所处理数据的子数据数量过少时,我们还是用快速排序的方法有点大材小用了,故针对于此我们也提出了 少量数据另谋他法的方法应对。

7.1 三数取中


  针对于所给目标数组第一位即为最小值或最大值而导致的时间复杂度浪费,我们可以取其数组首位,中位,末位三个数进行比较,选取其中处于中间位置的数,令其与数据首位进行交换。
  • 其代码如下所示:
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[right] > a[left])return left;elsereturn right;}else{if (a[mid] < a[right])return mid;else if (a[right] < a[left])return left;elsereturn right;}
}

7.2 少量数据另谋他法


  对于根据分界点所得到的新子数组,若其数据量较小,我们还是用快速排序的化有些大材小用,故当数据量较小时,我们一般使用其他排序方法,如冒泡排序或插入排序等,进行快速的排序。
  • 其代码如下所示
void BubbleSort(int* a, int n)
{for (int i = 0; i < n - 1; i++){for (int j = 0; j < n - i - 1; j++){if (a[j] > a[j + 1])Swap(&a[j], &a[j + 1]);}}
}if (right - left < 10){BubbleSort(a, right - left + 1);}




  至此快速排序的优化结束,其完整代码如下所示(前后指针法快速排序):

void QuickSort(int* a, int left, int right)
{if (left >= right)return;int mid = GetMid(a, left, right);Swap(&a[left], &a[mid]);if (right - left < 10){BubbleSort(a, right - left + 1);}else{int prev = left;int cur = prev + 1;int key = left;while (cur <= right){if (a[cur] < a[key] && a[++prev] != a[key])Swap(&a[cur], &a[prev]);cur++;}Swap(&a[key], &a[prev]);PartSort3(a, left, prev - 1);PartSort3(a, prev + 1, right);}
}




8、快速排序的特性总结


   快速排序的特性总结:
  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(logN)
  4. 稳定性:不稳定




9、结语


在这里插入图片描述

  十分感谢您观看我的原创文章。
  本文主要用于个人学习和知识分享,学习路漫漫,如有错误,感谢指正。
  如需引用,注明地址。

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

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

相关文章

【Java数据结构】详解Stack与Queue(三)

&#x1f512;文章目录&#xff1a; 1.❤️❤️前言~&#x1f973;&#x1f389;&#x1f389;&#x1f389; 2. 队列&#xff08;Queue&#xff09; 2.1队列的概念 2.2队列的方法 2.3队列的使用 2.4循环队列 循环队列的介绍 循环队列图 如何区分循环队列是满还是空…

外挂知识库的基本知识与内容

外挂知识库 1.什么是rag&#xff1f; RAG,即LLM在回答问题或生成文本时&#xff0c;会先从大量文档中检索出相关的信息&#xff0c;然后基于这些信息生成回答或文本&#xff0c;从而提高预测质量。 2.外挂知识库的实现思路 只用几十万量级的数据对大模型进行微调并不能很好…

第五十六周:文献阅读

目录 摘要 Abstract 文献阅读&#xff1a;应用于地表水总磷浓度预测的可解释CEEMDAN-FE-LSTM-Transformer混合模型 一、现有问题 二、提出方法 三、方法论 1、CEEMDAN&#xff08;带自适应噪声的完全包络经验模式分解&#xff09; 2、FE&#xff08;模糊熵 &#xff09…

Vue3【十】07使用ref创建基本类型的响应式数据以及ref和reactive区别

Vue3【十】07使用ref创建基本类型的响应式数据以及ref和reactive区别 ref 也可以创建对象类型的响应式数据&#xff0c;不过要使用.value ref 处理对象数据的时候&#xff0c;底层数据还是reactive格式的 reactive 重新分配一个新对象&#xff0c;会失去响应式可以使用Object.a…

保姆级 | MySQL的安装配置教程(非常详细)

一、下载Mysql 官网步骤 MySQLhttps://www.mysql.com/进入官网首页 点击DOWNLOADS 点击MySQL Community (GPL) Downloads 点击 小页面直接进入 MySQL :: Download MySQL Installerhttps://dev.mysql.com/downloads/installer/点击“Download”下载最新版本&#xff0c;其他…

【吊打面试官系列】MySQL 中 InnoDB 支持的四种事务隔离级别名称,以及逐级之间的区别?

大家好&#xff0c;我是锋哥。今天分享关于 【MySQL 中 InnoDB 支持的四种事务隔离级别名称&#xff0c;以及逐级之间的区别&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; MySQL 中 InnoDB 支持的四种事务隔离级别名称&#xff0c;以及逐级之间的区别&#xf…

碳素钢化学成分分析 螺纹钢材质鉴定 钢材维氏硬度检测

碳素钢的品种主要有圆钢、扁钢、方钢等。经冷、热加工后钢材的表面不得有裂缝、结疤、夹杂、折叠和发纹等缺陷。尺寸和允许公差必须符合相应品种国家标准的要求。 具体分类、按化学成分分类 &#xff1a; 碳素钢按化学成分&#xff08;即以含碳量&#xff09;可分为低碳钢、中…

机器学习笔记 - stable diffusion web-ui安装教程

一、Stable Diffusion WEB UI 屌丝劲发作了,所以本地调试了Stable Diffusion之后,就去看了一下Stable Diffusion WEB UI,网络上各种打包套件什么的好像很火。国内的也就这个层次了,老外搞创新,国内跟着屁股后面搞搞应用层,就叫大神了。 不扯闲篇了,我们这里从git源码直接…

问题:11单位内部人员对行政机关作出的行政处分不服,可申请行政复议. #其他#微信

问题&#xff1a;11单位内部人员对行政机关作出的行政处分不服,可申请行政复议. 参考答案如图所示

问题:脾梗塞时,下列情况最符合的是 #职场发展#知识分享#媒体

问题&#xff1a;脾梗塞时,下列情况最符合的是 A、脾肿大 B、脾区摩擦感 C、两者均有 D、两者均无 参考答案如图所示

uniapp视频组件层级太高,解决方法使用subNvue原生子体窗口

目录 前言 先看一下uniapp官网的原话&#xff1a; subNvue的一些参数介绍 subNvues使用方法&#xff1a; 绑定id 显示 subNvue 弹出层 subNvue.show() 参数信息 subNvue.hide() 参数信息 在使用subNvue 原生子体窗口 遇到的一些问题 前言 nvue 兼容性 以及使用方式 控…

把ROS程序作为桌面图标双击启动

1 写launch文件 把ROS程序写成一个launch文件&#xff0c;例如 powerline_with_rviz.launch <launch><!-- Load camera parameters --><rosparam file"$(find choose_powerline)/config/camera_params.yaml" command"load"/><!-- …

深入理解并应用KTT求解约束性极值问题

KT 很简单&#xff0c;口诀记心端&#xff0c;等式求最优&#xff0c;不等式验证——小飞打油 以后每期尝试编一句口诀&#xff0c;帮助大家记忆&#xff0c;可以是打油诗&#xff0c;也可以是类似“奇变偶不变&#xff0c;符号看象限”的口诀&#xff0c;如果编的不好&#xf…

Docker 常用命令以及镜像选择

目录 1.Docker基本组成 2.镜像选择 2.1、镜像推荐选择方案 2.2版本选择 3.Docker 命令 3.1镜像管理 拉取镜像&#xff1a; 列出镜像&#xff1a; 删除镜像&#xff1a; 构建镜像&#xff1a; 3.2容器管理 运行容器 列出运行中的容器和所有容器 停止容器 启动重启…

为什么要将Modbus转成MQTT

什么是Modbus Modbus 是一种串行通信协议&#xff0c;最初由Modicon&#xff08;现在的施耐德电气Schneider Electric&#xff09;于1979年开发&#xff0c;用于可编程逻辑控制器&#xff08;PLC&#xff09;之间的通信。Modbus协议设计简单&#xff0c;易于部署和维护&#xf…

从零入手人工智能(2)——搭建开发环境

1.前言 作为一名单片机工程师&#xff0c;想要转型到人工智能开发领域的道路确实充满了挑战与未知。记得当我刚开始这段旅程时&#xff0c;心中充满了迷茫和困惑。面对全新的领域&#xff0c;我既不清楚如何入手&#xff0c;也不知道能用人工智能干什么。正是这些迷茫和困惑&a…

M1Pro 使用跳板机

Mac (M1 Pro) 通过Iterm2 使用跳板机 1、由于堡垒机&#xff08;跳板机&#xff09;不能支持mac系统终端工具&#xff0c;只支持xshell等win生态。所以我们需要先安装iterm2 装iterms教程 这里头对rz、sz的配置不详细。我们可以这样配置&#xff1a; where iterm2-send-zmod…

Windows 11中删除分区的几种方法,总有一种适合你

序言 想从Windows 11 PC中删除一个分区,以便将空间重新分配给现有分区或创建一个新分区吗?我们将为你介绍删除Windows 11分区的多种方法。 删除Windows上的分区时会发生什么 删除分区时,Windows会擦除该分区的内容,并将该分区从电脑上的任何位置删除。你将丢失保存在该分…

单元测试覆盖率

什么是单元测试覆盖率 关于其定义&#xff0c;先来看一下维基百科上的一段描述&#xff1a; 代码覆盖&#xff08;Code coverage&#xff09;是软件测试中的一种度量&#xff0c;描述程序中源代码被测试的比例和程度&#xff0c;所得比例称为代码覆盖率。 简单来理解&#xff…

C语言实现map数据结构 key—value对应

1.首先43行 createKeyValuePair(char*key ,int value)这个函数就是给一个keyValuePair *pair的指针来通过内存分配将数据key和value存入这个pair指针所对应的内存空间 2.52行freeKeyValuePair这个函数是释放内存空间 3.头文件 struct结构体KeyValuePair就是一个指针一个值 4…