当前位置: 首页 > news >正文

一起学数据结构(11)——快速排序及其优化

     上篇文章中,解释了插入排序、希尔排序、冒泡排序、堆排序及选择排序的原理及具体代码实现本片文章将针对快速排序,快速排序的几种优化方法、快速排序的非递归进行解释。

目录

1. 快速排序原理解析以及代码实现:

2. 如何保证相遇位置的值一定小于所对应的值:

3 优化最坏情况下快速排序的时间复杂度:

4. 对于快速排序代码书写格式的优化(挖坑法):

5. 对于快速排序代码的另一种书写格式优化(前后指针法): 


1. 快速排序原理解析以及代码实现:

给定下面一个数组:

       对于快速排序,其中心思想就是首先选取一个值,一般选择数组中最左边的数据,例如本数组中的6。创建一个变量,来保存这个值所对应的下标,为了方便表达,将这个变量命名为key

      在确定了key之后,分别从两头遍历数组,定义两个变量用于遍历数组,其中,left=0right=n-1

     对于遍历数组的过程,需要分成两种情况来查看。其中,left用于从左向右遍历数组,right用于从右向左遍历数组。当数组从左向右进行遍历时,一旦遇到比key所对应的值大的数据,则停left在这个数据的所对应的下标处。即:

当数组从右向左进行遍历时,一旦遇到比key所对应的值小的数据,则right停在这个数据的所对应的下标处。即:

在找到了符合上面规定的值后,交换两个值在数组中的位置:

接着继续向下遍历,并且重复之前的动作。当leftright相遇时,停止循环,此时的数组可以表示为:

最后,将key所对应的值,与此时left或者right所对应的值进行一次交换,便完成了整个流程,用图形表示为:

 

 在进行了上面一整套流程后,此时的数组虽然还是无序,但是可以观察到,key所对应的值的右半部分的数组的值都大于key所对应的值。key左半部分数组的值都小于key所对应的值。

对于上述流程,可以用下面的代码进行表示:

 //部分快排int PortSort(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]);}Swap(&a[key], &a[left]);return left;}

对于上述给出的代码中,需要注意两个点:

1.在进行遍历数组寻找值的时候,必须先从右边开始遍历找小于key所对应的值的数据,再从左边找大于key所对应的值的数据,对于原因将会在文章的后面进行探讨

2.在遍历寻找值的while循环中,需要注意循环的两个条件,即left < right并且a[right] >= a[key],前面的条件是为了防止下面的两种情况:若从左向右遍历时,不存在比key对应的值大的数据,从右向左遍历时,不存在比key对应的值小的数据。以上两种情况均会导致left,right的范围超出数组下标的范围,对于第二个条件,如果不加上=,一旦在遍历的过程中,遇到了与key对应的值相等的值,会造成死循环。   

       完成了上述步骤后,数组依然是无序的,为了处理数组中其他的数据,需要利用到类似二叉树中递归的思想来实现:

       对于上述数组,他的数组左半部分的数据都是小于key所对应的值的,对于数组的右半部分的数据都是大于key所对应的值的,因此在下面的递归中,需要以key为分界线,key的左半部分为一组,右半部分为一组,对这两组值再次进行一次上面给出代码所对应的操作。

     例如,对于左半部分:

此时key所对应的值为3,按照上述代码进行操作后,数组可以表示为:

随后,再以key为分界线,分出左右部分,对于左右部分进行上述代码的操作,对于左半部分,进行一次操作后,可以表示为:

       此时,再向下进行递归,key的左半区间不存在,key的右半区间只有一个值。因此,对于上面两种情况,视作递归结束的条件。

      上面只是展示了每一次的递归中,数组的左半部分的情况,对于右半部分原理相同,这里不再进行赘述。当作伴部分,右半部分的递归都结束后,整个区间会变成有序的区间,即:

对于上述递归,可以用下列代码进行实现:

 void QuickSort(int* a, int begin, int end){if (begin >= end)return;int keyi = PortSort(a, begin, end);QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);}

测试函数如下:

void TestQuickSort()
{int e[] = { 6,1,2,7,9,3,4,5,10,8 };int size = sizeof(e) / sizeof(int);QuickSort(e, 0,size-1);printf("快速排序:");ArrayPrint(e, size);
}

结果如下:

2. 如何保证相遇位置的值一定小于key所对应的值:

       上面说到,在进行遍历寻找值这一步骤时,一定要先从右边开始向左遍历来找到比key对应的值小的值,再从左边向右开始遍历,来寻找比key对应值大的值,原因如下:

例如对于上面给出的数组,对于left,right相遇的前一步情况:

假设左边先进行遍历去寻找值,再从右边向左遍历来寻找值,则二者相遇的位置为:

此时,如果按照上面代码的内容对key对应的值和left位置对应的值进行交换,则:

此时,比key对应的值大的值不只是只存在右边。 

这里需要注意,先从右边往左边遍历的,对应的是key的位置在数组的左端。当key在数组的右端时,需要先从左边向右遍历。

3 优化最坏情况下快速排序的时间复杂度:

快速排序的时间复杂度一般都认为是O(nlogn),但是对于下面的一种情况:

前面说到,在快速排序中,当key取左端的值时,应该优先从右边开始遍历,对于例子中这种完全升序,或者说大部分区间都是升序的情况,每次从右端进行遍历时,都必须遍历到数组的最左边。因此,对于一个有n个数据的这样的数组来说,从右向左遍历的次数为n-1+n-1+n-3+.......次,这种情况下的快速排序的时间复杂度为O(n^2)。为了优化这种情况,这里引入三数取中的方法。代码如下:因为原理就是简单的两数相比,所以不做过多解释:

 //三数取中int GetMidi(int* a, int left, int right){int mid = (left + right) / 2;if (a[left] < a[mid]){if (a[right] > a[mid]){return mid;}else if (a[left] > a[right]){return left;}else{return right;}}else//(a[left] > a[mid];{if (a[mid] > a[right]){return mid;}else if (a[left] < a[right]){return left;}else{return right;}}}

 在构建了三数取中函数之后,需要对之前的快速排序代码进行修改。修改的部分为:首先在函数PortSort的开头创建一个变量mid用于接受三数取中函数GetMidi的返回值。接着,让mid对应的数值与后续key下标对应的数据(即最左端,或者最右端)用交换函数交换即可。

4. 对于快速排序代码书写格式的优化(挖坑法):

对于挖坑法,具体的实现原理如下:

首先还是确认key以及key所对应的值,例如在下面的例子中

(注:为了方便演示原理,下面的情况不包括三数取中,但是在书写代码时,使用三数取中的方法与上方的使用发放相同)

       首先确定一个key,这里按照左端处理,创建一个变量key,令key = 6,接着,确定key所在的下标就是第一个坑位。在确认了第一个坑位后,与上面的快速排序相同,都需要先从右端开始遍历来找到比key小的值,即:

随后,直接让right所指向的值覆盖到hole的位置,再让right指向的位置变成新的坑,即:

 再从左边向右进行遍历,找到比key小的值,并且让这个值覆盖到坑位hole中,再令下图中的left成为新的坑位:

 继续从右向左进行遍历,再从左向右遍历,直到left,right相遇为止,即:

最后,再令二者相遇的位置赋值key即可。然后再按照上面的思想递归即可。代码实现为:
 

//快排优化(挖坑法)int QuickDigSort(int* a, int begin, int end){int mid = GetMidi(a, begin, end);Swap(&a[begin], &a[mid]);int key = a[begin];int hole = begin;while (begin < end){//先从右边开始找小的while (begin < end && a[end] >= key){end--;}a[hole] = a[end];hole = end;//再从左边开始找大的while (begin < end && a[begin] <= key){begin++;}a[hole] = a[begin];hole = begin; }a[hole] = key;return hole;	}void QuickSort2(int* a, int begin, int end){if (begin >= end)return;int keyi = QuickDigSort(a, begin, end);QuickSort2(a, begin, keyi - 1);QuickSort2(a, keyi + 1, end);}

测试函数如下:

void TestQuickDigSort()
{int f[] = { 6,1,5,3,9,10,7,4,2,8 };int size = sizeof(f) / sizeof(int);QuickSort2(f, 0, size - 1);printf("快速排序(挖坑法):");ArrayPrint(f, size);
}

运行结果如下:

 

5. 对于快速排序代码的另一种书写格式优化(前后指针法): 

        对于前后指针法,就是通过控制两个指针,这里分别命名为prev,cur。通过控制这两个指针的动作时序来完成对于数组的排序,中心思想如下:

       对于指针cur的作用,就是用来寻找比key小的值( key的意义与上面相同),对于指针prev的动作时序,分为下面两种情况: 当cur没有遇到比key大的值时,prev一直紧跟着cur,当cur遇到比key大的值后,令prev的位置处于这个值的前面。

       继续令cur向后遍历,如果又找到了比key小的值,则交换这个值,与prev后面的值。

例如对于下面的数组:

按照上面所说的思想,在cur没有遇到比key大的值时,prev一直紧跟着cur运动,即:

此时,再向下运动,cur遇到了比key大的值,令cur继续向后遍历,直至找到比key小的值,令prev停在比key大的值的前面,即:

再向下运动后,cur遇到了比key小的值,令prev后面的值与比key小的值交换,即:

接着继续向下遍历,此时prev,cur指向的位置为:

此时再对两个指针指向的值进行交换,即:

在遍历结束时,数组如下:

 在结束遍历后,交换prev位置的值与key的值,即:

交换完后,比key小的值都位于prev左边,比 key大的值都位于prev右边。

总览上面的整个过程,当遇到了比key大的值后,两个指针便开始拉开差距。此时cur每次向后遍历,都会找到一个比key大的值,并且形成一个比key大的值的区间,在其期间,prev一直保持不动,直到cur遇到一个key小的值,prev在向后指向第一个比key大的值并且交换,此后cur每找到一个key小的值,都会把之前key大的值置换到后面,把key小的值置换到前面。

代码如下:

 //快速排序(前后指针法)int QuickTailSort(int* a, int begin, int end){int key = begin;int prev = begin, cur = begin+1;while (cur <= end){if (a[cur] < a[key]){Swap(&a[++prev], &a[cur]);}cur++;;}Swap(&a[key], &a[prev]);return prev;}

测试函数如下:

void TestQuickTailSort()
{int g[] = { 5,1,6,9,3,10,4,7,2,8 };int size = sizeof(g) / sizeof(int);QuickSort3(g, 0, size - 1);printf("快速排序(挖坑法):");ArrayPrint(g, size);
}

运行结果如下:

相关文章:

一起学数据结构(11)——快速排序及其优化

上篇文章中&#xff0c;解释了插入排序、希尔排序、冒泡排序、堆排序及选择排序的原理及具体代码实现本片文章将针对快速排序&#xff0c;快速排序的几种优化方法、快速排序的非递归进行解释。 目录 1. 快速排序原理解析以及代码实现&#xff1a; 2. 如何保证相遇位置的值一…...

Docker开箱即用,开发码农加分项部署技术拿下!

目录 Docker概述 效果呈现 镜像 & 镜像仓库 & 容器 镜像 DockerHub 配置国内源加速 容器 简单的命令解读 Docker基础 常用命令 案例演示 数据卷 什么是数据卷 数据卷命令 演示环节 匿名数据卷 案例演示 自定义挂载位置 案例演示 自定义镜像 镜像结构 Dockerfile …...

计算机算法分析与设计(16)---Dijkstra算法(含C++代码)

文章目录 一、知识概述1.1 算法描述1.2 例题分析 二、代码编写 一、知识概述 1.1 算法描述 1.2 例题分析 二、代码编写 输入&#xff1a;  第一行&#xff1a;图的顶点数n  第二行&#xff1a;图的边数k  第三行&#xff1a;算法起点begin&#xff0c;算法终点end  接下来…...

小团队之间有哪些好用免费的多人协同办公软件

在小团队协作中&#xff0c;选择适合的多人协同办公软件是提高工作效率和团队协作的重要一环。幸运的是&#xff0c;市场上有许多大多数功能都免费的多人协同办公软件&#xff0c;为小团队提供了强大的协作功能和便捷的工作环境。 在本文中&#xff0c;我将根据自己多年的在线…...

codeforces (C++ Morning)

题目&#xff1a; 翻译&#xff1a; 思路&#xff1a; 1、要将四位数显示&#xff0c;每次操作可以选择移动光标&#xff08;移动到相邻的位置&#xff09;或者显示数字&#xff0c;计算最少需要多少次操作。 2、用flag表示当前光标位置&#xff0c;sum为记录操作次数&#…...

Oracle数据库备份与恢复exp/imp命令

exp导出工具将数据库中数据备份压缩成一个二进制系统文件&#xff0c;可以在不同OS间迁移 可以导出用户所有对象以及对象中的数据&#xff1b;导出用户所有表或者指定的表&#xff1b;导出数据库中所有对象。 imp所执行的步骤&#xff1a; (1) create table --新建表 (2) inser…...

何为心理承受能力?如何提高心理承受能力?

心理承受能力&#xff0c;也可以理解为人的抗压能力&#xff0c;指的是承受压力&#xff0c;承受逆境的能力。人的一生其实就是在不断的解决问题&#xff0c;见招拆招&#xff0c;遇到问题解决问题&#xff0c;在我们不断学习和锻炼的过程中&#xff0c;提高了我们解决问题的效…...

Seata学习

Seata Seata 是一款开源的分布式事务解决方案&#xff0c;致力于在微服务架构下提供高性能和简单易用的分布式事务服务。 官网地址&#xff1a;https://seata.io/zh-cn/index.html 为什么会产生分布式事务&#xff1f; 示例&#xff1a;用户下单后需要创建订单&#xff0c;同时…...

探索数据结构世界之排序篇章(超级详细,你想看的都有)

-文章开头必看 1.&#xff01;&#xff01;&#xff01;本文排序默认都是排升序 2.排序是否稳定值指指排完序之后相同数的相对位置是否改变 3.代码相关解释我都写在注释中了&#xff0c;方便对照着看 1.插入排序1.1直接插入排序1.2希尔排序1.2.1单趟1.2.2多趟基础版——排完一…...

偶数科技发布实时湖仓数据平台Skylab 5.3版本

近日&#xff0c; 偶数发布了最新的实时湖仓数据平台 Skylab 5.3 版本。Skylab包含七大产品&#xff0c;分别为云原生分布式数据库 OushuDB、数据分析与应用平台 Kepler、数据资产管理平台 Orbit、自动化机器学习平台 LittleBoy、数据工厂 Wasp、数据开发与调度平台 Flow、系统…...

vant组件是使用?

首先 在vue项目中使用的时候 要先下载组件 使用npm安装 # Vue 3 项目&#xff0c;安装最新版 Vant npm i vant# Vue 2 项目&#xff0c;安装 Vant 2 npm i vantlatest-v2 使用yarn安装或pnpm # 通过 yarn 安装 yarn add vant# 通过 pnpm 安装 pnpm add vant 在框架中引入即…...

CSP-S 2023 游记

开题&#xff0c;首先先把除了第三题的所有题看了一遍。&#xff08;由于第三题太长&#xff0c;先放着后面再看&#xff09; 决定顺序先把一二题做了。 看第一题&#xff0c;小小思考了一手&#xff0c;发现暴力可做&#xff0c;于是飞速码完&#xff0c;小小对拍一下&#…...

关于Git的入门教程(附GitHub和Gitee的使用方法)

一. Git 概述 Git是一个免费的、开源的分布式版本控制系统&#xff0c;可以快速高效地处理从小型到大型的各种项目。Git易于学习、占地面积小、性能极快。它具有廉价的本地库&#xff0c;方便的暂存区域和多个工作流分支等特性。其性能优于Subversion、CVS、Perforce和ClearCas…...

C# winform如何实现数据的保存和读取

在c#winform中我们在写程序时&#xff0c;经常需要进行数据处理&#xff0c;那么数据如何保存和读取&#xff08;下面我们通过序列化和反序列化的方式来实现&#xff09; 第一步: 我们建立一个winform窗体 第二步: 构建一个外部实体类&#xff08;Student类&#xff09; 第…...

【Java基础面试四十一】、说一说你对static关键字的理解

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a;说一说你对static关键字…...

istio介绍(二)

5. kubesphere istio使用 5.1 整体架构 ks-account 提供用户、权限管理相关的 APIks-apiserver 整个集群管理的 API 接口和集群内部各个模块之间通信的枢纽&#xff0c;以及集群安全控制ks-apigateway 负责处理服务请求和处理 API 调用过程中的所有任务ks-console 提供 KubeSp…...

中文编程开发语言工具构件说明:屏幕截取构件的编程操作

屏幕截取 用于截取指定区域的图像。 图 标&#xff1a; 构件类型&#xff1a;不可视 重要属性 l 截取类型 枚举型&#xff0c;设置在截取屏幕时的截取类型。包括&#xff1a;全屏幕、指定区域、活动窗口三种。当全屏幕截取时相当于执行了硬拷屏&#xff08;PrintScre…...

selenium多窗口、多iframe切换、alert、3种等待

1、多标签/多窗口之间的切换 场景&#xff1a; 在页面操作过程中有时候点击某个链接会弹出新的窗口&#xff0c;这时就需要切换到新打开的窗口上进行操作。这种情况下&#xff0c;需要识别多标签或窗口的情况。 操作方法&#xff1a; switch_to.window()方法&#xff1a;切换…...

物联网AI MicroPython传感器学习 之 RTC时钟模块

学物联网&#xff0c;来万物简单IoT物联网&#xff01;&#xff01; 一、产品简介 DS1302 是DALLAS 公司推出的涓流充电时钟芯片&#xff0c;内含有一个实时时钟/日历和31字节静态RAM&#xff0c;实时时钟/日历电路提供秒、分、时、日、周、月、年的信息&#xff0c;每月的天数…...

Mac安装nginx(Homebrew)

查看需要安装 nginx 的信息 brew info nginxDocroot 默认为 /usr/local/var/www 在 /opt/homebrew/etc/nginx/nginx.conf 配置文件中默认端口被配置为8080&#xff0c;从而使 nginx 运行时不需要加 sudo nginx将在 /opt/homebrew//etc/nginx/servers/ 目录中加载所有文件 …...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具

作者&#xff1a;来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗&#xff1f;了解下一期 Elasticsearch Engineer 培训的时间吧&#xff01; Elasticsearch 拥有众多新功能&#xff0c;助你为自己…...

【解密LSTM、GRU如何解决传统RNN梯度消失问题】

解密LSTM与GRU&#xff1a;如何让RNN变得更聪明&#xff1f; 在深度学习的世界里&#xff0c;循环神经网络&#xff08;RNN&#xff09;以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而&#xff0c;传统RNN存在的一个严重问题——梯度消失&#…...

全球首个30米分辨率湿地数据集(2000—2022)

数据简介 今天我们分享的数据是全球30米分辨率湿地数据集&#xff0c;包含8种湿地亚类&#xff0c;该数据以0.5X0.5的瓦片存储&#xff0c;我们整理了所有属于中国的瓦片名称与其对应省份&#xff0c;方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

测试markdown--肇兴

day1&#xff1a; 1、去程&#xff1a;7:04 --11:32高铁 高铁右转上售票大厅2楼&#xff0c;穿过候车厅下一楼&#xff0c;上大巴车 &#xffe5;10/人 **2、到达&#xff1a;**12点多到达寨子&#xff0c;买门票&#xff0c;美团/抖音&#xff1a;&#xffe5;78人 3、中饭&a…...

c++ 面试题(1)-----深度优先搜索(DFS)实现

操作系统&#xff1a;ubuntu22.04 IDE:Visual Studio Code 编程语言&#xff1a;C11 题目描述 地上有一个 m 行 n 列的方格&#xff0c;从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子&#xff0c;但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

渲染学进阶内容——模型

最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...

el-switch文字内置

el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...

06 Deep learning神经网络编程基础 激活函数 --吴恩达

深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...

【Go语言基础【13】】函数、闭包、方法

文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数&#xff08;函数作为参数、返回值&#xff09; 三、匿名函数与闭包1. 匿名函数&#xff08;Lambda函…...

LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》

这段 Python 代码是一个完整的 知识库数据库操作模块&#xff0c;用于对本地知识库系统中的知识库进行增删改查&#xff08;CRUD&#xff09;操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 &#x1f4d8; 一、整体功能概述 该模块…...