【数据结构】排序算法---快速排序

在这里插入图片描述

文章目录

  • 1. 定义
  • 2. 算法步骤
  • 3. 动图演示
  • 4. 性质
  • 5. 递归版本代码实现
    • 5.1 hoare版本
    • 5.2 挖坑法
    • 5.3 lomuto前后指针
  • 6. 优化
  • 7. 非递归版本代码实现
  • 结语

1. 定义

快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 O ( n l o g n ) Ο(nlogn) O(nlogn)次比较。在最坏状况下则需要 O ( n 2 ) Ο(n^2) O(n2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 O ( n l o g n ) Ο(nlogn) O(nlogn)算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。

快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。

快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义,就是快,而且效率高!它是处理大数据最快的排序算法之一了。虽然最坏的情况下的时间复杂度达到了 O ( n 2 ) O(n^2) O(n2),但是人家就是优秀,在大多数情况下都比平均时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)的排序算法表现要更好,可是这是为什么呢? 在《算法艺术与信息学竞赛》上有给出满意的答案:

快速排序的最坏运行情况是 O ( n 2 ) O(n^2) O(n2),比如说顺序数列的快排。但它的平摊期望时间是 O ( n l o g n ) O(nlogn) O(nlogn),且 O ( n l o g n ) O(nlogn) O(nlogn)记号中隐含的常数因子很小,比复杂度稳定等于 O ( n l o g n ) O(nlogn) O(nlogn)的归并排序要小很多。所以,对绝大多数顺序性较弱的随机数列而言,快速排序总是优于归并排序。

2. 算法步骤

快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下(以升序为例):

  1. 从数列中挑出一个元素,称为 “基准”(pivot);

  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;

  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

3. 动图演示

在这里插入图片描述

4. 性质

稳定性

快速排序是一种不稳定的排序算法。

空间复杂度

冒泡排序的空间复杂度为 O ( l o g n ) O(logn) O(logn)

时间复杂度

快速排序的最优时间复杂度和平均时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),最坏时间复杂度为 O ( n 2 ) O(n^2) O(n2)

  • 对于最优情况,每一次选择的分界值都是序列的中位数,此时算法时间复杂度满足的递推式为 T ( n ) = 2 T ( n 2 ) + O ( n ) T(n)=2T({n \over 2})+O(n) T(n)=2T(2n)+O(n),由主定理, T ( n ) = O ( n l o g n ) T(n)=O(nlogn) T(n)=O(nlogn)

  • 对于最坏情况,每一次选择的分界值都是序列的最值,此时算法时间复杂度满足的递推式为 T ( n ) = T ( n − 1 ) + O ( n ) T(n)=T({n-1})+O(n) T(n)=T(n1)+O(n),累加可得 T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2)

  • 对于平均情况,每一次选择的分界值可以看作是等概率随机的。

在实践中,几乎不可能达到最坏情况,而快速排序的内存访问遵循局部性原理,所以多数情况下快速排序的表现大幅优于堆排序等其他复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)的排序算法。

5. 递归版本代码实现

快速排序实现主框架

//快速排序
void QuickSort(int* a, int left, int right)
{if (left >= right) {return;}//_QuickSort⽤于按照基准值将区间[left,right)中的元素进⾏划分int meet = _QuickSort(a, left, right);QuickSort(a, left, meet - 1);QuickSort(a, meet + 1, right);
}

将区间中的元素进行划分的_QuickSort方法主要有以下几种实现方式:

5.1 hoare版本

算法思路

(1)创建左右指针,确定基准值

(2)从右向左找出比基准值小的数据,从左向右找出基准值大的数据,左右指针数据交换,进入下次循环

问题1:为什么跳出循环后right位置的值一定不大于key?

当left > right时,即right走到left的左侧,而left扫描过的数据均不大于key,因此right此时指向的数据一定不大于key

在这里插入图片描述

问题2:为什么left 和 right指定的数据和key值相等时也要交换?

相等的值参与交换确实有一些额外消耗。实际还有各种复杂的场景,假设数组中的数据大量重复时,无法进行有效的分割排序。

在这里插入图片描述

代码

int _QuickSort(int* a, int left, int right)
{int begin = left;int end = right;int keyi = left;++left;while (left <= right){// 右边找小while (left <= right && a[right] > a[keyi]){--right;}// 右边找小while (left <= right && a[left] < a[keyi]){++left;}if (left <= right){swap(&a[left++], &a[right--]);}}swap(&a[keyi], &a[right]);return right;
}

5.2 挖坑法

算法思路

创建左右指针。首先从右向左找出比基准小的数据,找到后立即放入左边坑中,当前位置变为新的"坑",然后从左向右找出比基准大的数据,找到后立即放入右边坑中,当前位置变为新的"坑",结束循环后将最开始存储的分界值放入当前的"坑"中,返回当前"坑"下标(即分界值下标)

在这里插入图片描述

代码

int _QuickSort(int* a, int left, int right)
{int mid = a[left];int hole = left;int key = a[hole];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;
}

5.3 lomuto前后指针

算法思路:创建前后指针,从左往右找比基准值小的进行交换,使得小的都排在基准值的左边。

在这里插入图片描述

代码

int _QuickSort(int* a, int left, int right)
{int prev = left, cur = left + 1;int key = left;while (cur <= right){if (a[cur] < a[key] && ++prev != cur){swap(&a[cur], &a[prev]);}++cur;}swap(&a[key], &a[prev]);return prev;
}

6. 优化

朴素优化思想

较为常见的优化思路有以下三种:

  • 通过 三数取中(即选取第一个、最后一个以及中间的元素中的中位数) 的方法来选择两个子序列的分界元素(即比较基准)。这样可以避免极端数据(如升序序列或降序序列)带来的退化;
  • 序列较短时,使用 插入排序 的效率更高;
  • 每趟排序后,将与分界元素相等的元素聚集在分界元素周围,这样可以避免极端数据(如序列中大部分元素都相等)带来的退化。

下面介绍几种较为成熟的快速排序优化方式:

  • 三路快速:三路快速排序(英语:3-way Radix Quicksort)是快速排序和 [基数排序] 的混合。它的算法思想基于 [荷兰国旗问题]的解法。
  • 内省排序(英语:Introsort 或 Introspective sort)是快速排序和 [堆排序]的结合,由 David Musser 于 1997 年发明。内省排序其实是对快速排序的一种优化,保证了最差时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

具体相关介绍看文章:https://oi-wiki.org/basic/quick-sort/

7. 非递归版本代码实现

非递归版本的快速排序需要借助数据结构:栈

void QuickSortNonR(int* a, int left, int right)
{ST st;STInit(&st);STPush(&st, right);STPush(&st, left);while (!STEmpty(&st)){int begin = STTop(&st);STPop(&st);int end = STTop(&st);STPop(&st);// 单趟int keyi = begin;int prev = begin;int cur = begin + 1;while (cur <= end){if (a[cur] < a[keyi] && ++prev != cur)Swap(&a[prev], &a[cur]);++cur;}Swap(&a[keyi], &a[prev]);keyi = prev;// [begin, keyi-1] keyi [keyi+1, end]if (keyi + 1 < end){STPush(&st, end);STPush(&st, keyi + 1);}if (begin < keyi - 1){STPush(&st, keyi - 1);STPush(&st, begin);}}STDestroy(&st);
}

结语

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下。

也可以点点关注,避免以后找不到我哦!

Crossoads主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是作者前进的动力!

带你初步了解排序算法:https://blog.csdn.net/2301_80191662/article/details/142211265
直接插入排序:https://blog.csdn.net/2301_80191662/article/details/142300973
希尔排序:https://blog.csdn.net/2301_80191662/article/details/142302553
直接选择排序:https://blog.csdn.net/2301_80191662/article/details/142312028
堆排序:https://blog.csdn.net/2301_80191662/article/details/142312338
冒泡排序:https://blog.csdn.net/2301_80191662/article/details/142324131
快速排序:https://blog.csdn.net/2301_80191662/article/details/142324307
归并排序:https://blog.csdn.net/2301_80191662/article/details/142350640
计数排序:https://blog.csdn.net/2301_80191662/article/details/142350741
十大经典排序算法总结与分析:https://blog.csdn.net/2301_80191662/article/details/142211564

在这里插入图片描述

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

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

相关文章

记录word转xml文件踩坑

word文件另存为xml文件后&#xff0c;xml文件乱码 解决方法&#xff1a; 1.用word打开.docx文件 2.另存为xml文件 3.点击工具 -> Web选项 -> 编码&#xff0c;选择UTF-8 4.点击确定 5.使用notpad打开xml文件 6.使用xml tool进行xml格式化即可。

【逐行注释】自适应Q和R的AUKF(自适应无迹卡尔曼滤波),附下载链接

文章目录 自适应Q的KF逐行注释的说明运行结果部分代码各模块解释 自适应Q的KF 自适应无迹卡尔曼滤波&#xff08;Adaptive Unscented Kalman Filter&#xff0c;AUKF&#xff09;是一种用于状态估计的滤波算法。它是基于无迹卡尔曼滤波&#xff08;Unscented Kalman Filter&am…

VMware vCenter Server 8.0U3b 发布下载,新增功能概览

VMware vCenter Server 8.0U3b 发布下载&#xff0c;新增功能概览 Server Management Software | vCenter 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-vcenter-8-u3/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysi…

无人机之控制距离篇

无人机的控制距离是一个复杂且多变的概念&#xff0c;它受到多种因素的共同影响。以下是对无人机控制距离及其影响因素的详细分析&#xff1a; 一、无人机控制距离的定义 无人机控制距离指的是遥控器和接收机之间的最远传输距离。这个距离决定了无人机在操作者控制下能够飞行的…

51单片机-直流电机(PWM:脉冲宽度调制)实验-会呼吸的灯直流电机调速

作者&#xff1a;Whappy&#xff08;菜的扣脚&#xff09; 脉冲宽度调制&#xff08;Pulse Width Modulation&#xff0c;PWM&#xff09;是一种通过调节信号的占空比来控制功率输出的技术。它主要通过改变脉冲信号的高电平持续时间相对于低电平的时间来调节功率传递给负载的量…

影刀RPA实战:网页爬虫之携程酒店数据

1.实战目标 大家对于携程并不陌生&#xff0c;我们出行定机票&#xff0c;住酒店&#xff0c;去旅游胜地游玩&#xff0c;都离不开这样一个综合性的网站为我们提供信息&#xff0c;同时&#xff0c;如果你也是做旅游的公司&#xff0c;那携程就是一个业界竞争对手&#xff0c;…

git 生成和查看密钥

项目场景&#xff1a; 在前端项目开发中&#xff0c;经常会用到git。一般的小公司很少去设置git令牌或者密钥&#xff1b;而在一些大公司&#xff0c;会用到这个。今天主要整理下git如何生成和查看密钥。 密钥 1、生成密钥 cat ~/.ssh/id_rsa.pub 2、查看密钥 ssh-keygen…

Istio下载及安装

Istio 是一个开源的服务网格&#xff0c;用于连接、管理和保护微服务。以下是下载并安装 Istio 的步骤。 官网文档&#xff1a;https://istio.io/latest/zh/docs/setup/getting-started/ 下载 Istio 前往Istio 发布页面下载适用于您的操作系统的安装文件&#xff0c;或者自动…

前端动画库大比拼:为何选择Velocity.js

前端动画库大比拼&#xff1a;为何选择Velocity.js 前言 在现代网页设计中&#xff0c;动画效果是提升用户体验的重要手段。 Velocity.js: 一个与 jQuery 动画 API 兼容的动画引擎&#xff0c;以其卓越的性能和丰富的功能&#xff0c;成为了开发者的好工具。 本文将详细介绍…

数字逻辑电路-加法器

目录 半加器和全加器 半加器 ​全加器 集成全加器 利用全加器实现二进制的乘法功能 加法器 半加器和全加器 半加器 不考虑低位进位的加法。 本位为s&#xff0c;进位为c。 全加器 多了一个相邻低位来的进位数。 集成全加器 左上角和右下角那两个是不用的。 利用全加器…

「iOS」——单例模式

iOS学习 前言单例模式的概念单例模式的优缺点单例模式的两种模式懒汉模式饿汉模式单例模式的写法 总结 前言 在一开始学习OC的时候&#xff0c;我们初步接触过单例模式。在学习定时器与视图移动的控件中&#xff0c;我们初步意识到单例模式的重要性。对于我们需要保持的控件&a…

Python基础(七)——PyEcharts数据分析(面向对象版)

四、使用PyEcharts数据分析案例&#xff08;面向对象版&#xff09; 【前言&#xff1a;为了巩固之前的Python基础知识&#xff08;一&#xff09;到&#xff08;五&#xff09;&#xff0c;并为后续使用Python作为数据处理的好帮手&#xff0c;我们一起来用面向对象的思想来理…

基于ESP32S3的链接大语言模型对话模块

本实物模块从实物外观、模块组成、API申请及功能说明四部分来介绍这款基于ESP32S3的大语言模型对话模块。 1、实物外观 2、模块介绍 本硬件平台主要由三个模块组成&#xff0c;包括MAX9814录音模块、MAX98357音频功放模块和ESP32S3模块。如下图所示。 MAX9814录音模块&#…

C#程序员的堕落从nuget开始:将自己的代码发布到nuget

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 源码指引&#xff1a;github源…

【Android 13源码分析】WindowContainer窗口层级-2-构建流程

在安卓源码的设计中&#xff0c;将将屏幕分为了37层&#xff0c;不同的窗口将在不同的层级中显示。 对这一块的概念以及相关源码做了详细分析&#xff0c;整理出以下几篇。 【Android 13源码分析】WindowContainer窗口层级-1-初识窗口层级树 【Android 13源码分析】WindowCon…

房产销售系统:SpringBoot技术应用案例

第二章关键技术的研究 2.1相关技术 房产销售系统是在Java MySQL开发环境的基础上开发的。Java是一种服务器端脚本语言&#xff0c;易于学习&#xff0c;实用且面向用户。全球超过35&#xff05;的Java驱动的互联网站点使用Java。MySQL是一个数据库管理系统&#xff0c;因为它的…

代理导致的git错误

问题&#xff1a; 今天在clone时出现如下错误&#xff1a; fatal: unable to access https://github.com/NirDiamant/RAG_Techniques.git/: Failed to connect to 127.0.0.1 port 10089 after 2065 ms: Couldnt connect to server真是让人感到奇怪&#xff01;就在前天&#…

伪工厂模式制造敌人

实现效果 1.敌人方实现 敌人代码 using UnityEngine; using UnityEngine.UI;public class EnemyBasics : MonoBehaviour {public int EnemySpeed { get; internal set; }public int EnemyAttackDistance { get; internal set; }public int EnemyChaseDistance { get; interna…

初识 C++ ( 1 )

引言&#xff1a;大家都说c是c的升级语言。我不懂这句话的含义后来看过解释才懂。 一、面向过程语言和面向对象语言 我们都知道C语言是面向过程语言&#xff0c;而C是面向对象语言&#xff0c;说C和C的区别&#xff0c;也就是在比较面向过程和面向对象的区别。 1.面向过程和面向…

WebGL系列教程九(动画)

目录 1 前言2 绘制立方体并进行纹理映射3 动画思路4 开始绘制4.1 在顶点着色器中声明旋转矩阵4.2 获取旋转矩阵变量并进行赋值4.3 计算角度4.4 每一帧都去绘制4.5 效果4.6 完整代码 5 总结 1 前言 上一篇我们讲了WebGL中的基础语法&#xff0c;现在我们已经讲过了三维物体的绘制…