【数据结构】快速排序(用递归)

大家好,我是苏貝,本篇博客带大家了解快速排序,如果你觉得我写的还不错的话,可以给我一个赞👍吗,感谢❤️
在这里插入图片描述


目录

  • 一. 基本思想
  • 二. 快速排序
    • 2.1 hoare版本
    • 2.2 挖坑法
    • 2.3 前后指针法
    • 2.4 快速排序优化
      • 三数取中法取key(hoare版本)
      • 小区间优化

一. 基本思想

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后左右子序列重复该过程,直到所有元素都排列在相应位置上为止。


二. 快速排序

2.1 hoare版本

在这里插入图片描述

思路:

1.让key==数组在特定区间的第一个元素的下标
2.定义变量left和right,它们分别初始化为数组在特定区间的首元素和最后一个元素的下标,right先向前走,找到比key小的位置后停下;left再走,找到比key大的位置后停下;交换下标为right和left数组元素的位置。这样大的值就被放在后面,小的值就被放在前面
3.等到left和right相遇时,将相遇位置a[left]和下标为key的元素a[key]交换位置(能保证a[left]<a[key],具体原因下面会讲)
4.1-3步完成后,相遇位置即a[key]左边全是<=它的,右边都是>=它的
5.递归a[key]的左右子树,让它们经历1-3步

在这里插入图片描述

上面这幅图展示了将一个元素排序的步骤,后面还要排序6的左边和右边,我们可以将它当成一个二叉树来处理。第二次我们先来排6的左子树
在这里插入图片描述

第三次我们再来排3的左子树

在这里插入图片描述

2的左右子树分别为一个元素和空,所以不需要再递归下去。这两种情况用代码实现为:

if (begin >= end)return;

再递归3的右子树和6的右子树,思路一样,就不再赘述了

相遇位置的值一定小于下标为key的数组元素的原因:
在这里插入图片描述

int PartSort1(int* a, int begin, int end)
{int key = begin;int left = begin;int right = end;//right先走,left后走,right找小,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;
}void QuickSort(int* a, int begin, int end)
{if (begin >= end)return;int key = PartSort1(a, begin, end);QuickSort(a, begin, key - 1);QuickSort(a, key + 1, end);
}

2.2 挖坑法

在这里插入图片描述

思路:

在这里插入图片描述

挖坑法实际上与hoare方法差不多,只是可能更好理解一些。接下来也是递归6的左右子树,不多说了

int PartSort2(int* a, int begin, int end)
{int left = begin;int right = end;int hole = begin;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;
}void QuickSort(int* a, int begin, int end)
{if (begin >= end)return;int key = PartSort2(a, begin, end);QuickSort(a, begin, key - 1);QuickSort(a, key + 1, end);
}

2.3 前后指针法

在这里插入图片描述

思路:

1.定义3个变量key,prev和cur来表示数组的下标,prev和key置为第一个元素的下标,cur置为第二个元素的下标
2.当a[cur]>=a[key]时,cur++
3.当a[cur]<a[key]时,prev++,交换下标为prev和cur的元素,再cur++
4.如果cur>end(最后一个元素的下标),交换下标为prev和key的元素
5.递归a[key]的左右子树,让它们经历1-4步

在这里插入图片描述

接下来也是递归6的左右子树,不多说了

int PartSort3(int* a, int begin, int end)
{int key = begin;int prev = begin;int cur = begin + 1;while (cur <= end){if(a[cur]<a[key]){prev++;Swap(&a[prev], &a[cur]);}cur++;}Swap(&a[prev], &a[key]);return prev;
}void QuickSort(int* a, int begin, int end)
{if (begin >= end)return;int key = PartSort3(a, begin, end);QuickSort(a, begin, key - 1);QuickSort(a, key + 1, end);}

2.4 快速排序优化

1

三数取中法取key(hoare版本)

当数组本来就是有序数组(如升序)时,hoare版本的排序后的key相当于只有右子树,没有左子树。这样在数组元素较多时(如10000个),在debug条件下可能会因为栈溢出而报错(在release条件下不会,因为release对递归建立栈帧的优化已经足够好了)

下面是测试排序性能的函数,将用希尔排序排序好的数组a1再用快速排序,发现会报错,原因:栈溢出

void TestOP()
{srand(time(0));const int N = 10000;int* a1 = (int*)malloc(sizeof(int) * N);for (int i = 0; i < N; ++i){a1[i] = rand();}int begin1 = clock();ShellSort(a1, N);int end1 = clock();int begin2 = clock();QuickSort(a1, 0, N-1);int end2 = clock();printf("InsertSort:%d\n", end1 - begin1);printf("ShellSort:%d\n", end2 - begin2);free(a1);
}

在这里插入图片描述

那下面我们来优化hoare版本的快速排序。我们不再取数组第一个元素为key,而是取a[begin],a[end]和a[midi](midi是中间元素的下标)三者中的中位数。如何实现呢?先找到中位数的下标,再交换第一个元素和中位数的位置,再让key==begin,此时a[key]就不会是最小的元素了

int GetMidi(int* a, int begin, int end)
{int midi = (begin + end) / 2;if (a[begin] > a[midi]){if (a[midi] > a[end])return midi;else if (a[end] > a[begin])return begin;elsereturn end;}else{if (a[end] > a[midi])return midi;else if (a[begin] > a[end])return begin;elsereturn end;}
}int PartSort1(int* a, int begin, int end)
{int midi = GetMidi(a, begin, end);Swap(&a[begin], &a[midi]);int key = begin;int left = begin;int right = end;//right先走,left后走,right找小,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;
}

2

小区间优化

假设每次的key的最终位置都是中间位置,那么为了将最后7个数排好序,总共要递归7次,这种代价还是有些大的,所以我们选择当要排列的数组的元素比较少时,采用直接插入排序
在这里插入图片描述
那当要排列的数组的元素小于多少时用直接插入排序呢?我们一般选择在树的倒数前3排开始,因为这3排(如果是满二叉树),那么会占据整个树的将近90%的元素。因为最后一层的元素个数为2^(h-1),总元素个数为2 ^h-1,所以将近占了一半的元素,倒数第二排是倒数第一排的一半,所以是25%……基于这种原因,我们最后选择当要排列的数组的元素小于10时用直接插入排序,当然,你也可以选择其他的值
在这里插入图片描述

代码如下,你能发现有什么问题吗?

void QuickSort1(int* a, int begin, int end)
{if (begin >= end)return;if (end - begin + 1 < 10)InsertSort(a, end - begin + 1);else{int key = PartSort1(a, begin, end);QuickSort1(a, begin, key - 1);QuickSort1(a, key + 1, end);}
}

直接插入排序本来是从begin的位置开始到往后的end - begin + 1个元素结束,但是上面的代码是从下标为0的位置开始,所以将它改正

void QuickSort1(int* a, int begin, int end)
{if (begin >= end)return;if (end - begin + 1 < 10)InsertSort(a + begin, end - begin + 1);else{int key = PartSort1(a, begin, end);QuickSort1(a, begin, key - 1);QuickSort1(a, key + 1, end);}
}void QuickSort2(int* a, int begin, int end)
{if (begin >= end)return;int key = PartSort1(a, begin, end);QuickSort2(a, begin, key - 1);		QuickSort2(a, key + 1, end);
}

再使用测试排序性能的函数看看优化后与优化前的差别

void TestOP()
{srand(time(0));const int N = 10000;int* a1 = (int*)malloc(sizeof(int) * N);int* a2 = (int*)malloc(sizeof(int) * N);for (int i = 0; i < N; ++i){a1[i] = rand();a2[i] = a1[i];}int begin1 = clock();QuickSort1(a1, 0, N - 1);int end1 = clock();int begin2 = clock();QuickSort2(a2, 0, N - 1);int end2 = clock();printf("QuickSort1(优化后):%d\n", end1 - begin1);printf("QuickSort2:%d\n", end2 - begin2);free(a1);free(a2);
}

在debug条件下的结果:

我们发现,优化后只是比没有优化快了2毫秒,好像并没有很优秀。这也是正常的,毕竟快速排序本身就是一个较好的排序,相当于你本身就靠了93分,再让你提高5分,是不是也不容易呢?


好了,那么本篇博客就到此结束了,如果你觉得本篇博客对你有些帮助,可以给个大大的赞👍吗,感谢看到这里,我们下篇博客见❤️

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

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

相关文章

Python学习(一)

Python环境下载安装 安装略 验证安装结果与编写第一个Python程序

Vue 实现带拖动功能的时间轴

1.效果图 2. 当使用timeline-slider-vue组件时&#xff0c;你可以设置以下属性&#xff1a; date&#xff1a;用于设置时间轴滑块的初始日期&#xff0c;格式通常为 YYYY-MM-DD。 mask&#xff1a;一个布尔值&#xff0c;用于控制是否显示背景遮罩。 markDate&#xff1a;一…

需求:实现一个类似打印的效果(文字一个字一个字的输出)

实现效果&#xff1a; 需求&#xff1a;最近接到这么一个需求&#xff0c;ai机器人回复的问题&#xff0c;后端是通过websocket每隔一段事件返回数据&#xff0c;前端拿到数据后直接渲染&#xff0c;现在需要做到一个效果&#xff0c;后端返回的结果前端需要一个一个文字的输出…

MultiArch与Ubuntu/Debian 的交叉编译

返回&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a;基于ARM 的Linux系统的交叉编译 下一篇&#xff1a;MultiArch与Ubuntu/Debian 的交叉编译 警告&#xff1a; 本教程可能包含过时的信息。 什么是“MultiArch” OpenCV 可能…

【Canvas与艺术】暗蓝网格汽车速度仪表盘

【关键点】 采用线性渐变色&#xff0c;使上深下浅的圆有凹下效果&#xff0c;使上浅下深的圆有凸起效果&#xff0c;两者结合就有立体圆钮的感觉。 【图例】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type&quo…

php搭建websocket

1.项目终端执行命令&#xff1a;composer require topthink/think-worker 2.0.x 2.config多出三个配置文件&#xff1a; 3.当使用php think worker:gateway命令时&#xff0c;提示不支持Windows。 4.打包项目为zip格式 5.打包数据库 6.阿里云创建记录 7.宝塔面板新增站点…

win10如何录制视频?解锁屏幕录制新姿势!

在Windows 10操作系统中&#xff0c;视频录制已经成为一项非常实用的功能。无论是进行在线教育、游戏直播&#xff0c;还是制作教学视频、会议记录&#xff0c;一款易于使用且功能强大的录屏软件都是必不可少的。在本文中&#xff0c;我们将向您介绍win10如何录制视频的三种方法…

一枝独秀,基于区块链共益型短视频平台享视,真的能抗衡各大短视频平台吗

早在2006年&#xff0c;全球非营利性组织共益实验室(B Lab)就提出了共益企业(B Corp)的概念&#xff0c;致力于推动商业向善。 但时至今日&#xff0c;共益企业发展仍处初级阶段&#xff0c;而且只强调要对社区、员工、环境、供应商、消费者等利益相关者负责&#xff0c;认定标…

【协议-HTTPS】

https https是在http协议的基础上&#xff0c;添加了SSL/TLS握手以及数据加密传输&#xff0c;也属于应用层协议。 httpshttp加密认证完整性保护 https交互图&#xff1a; HTTPS的整体过程分为证书验证和数据传输阶段&#xff1a; ① 证书验证阶段 浏览器发起 HTTPS 请求 服务…

电脑不能读取移动硬盘,但是可以读取U盘解决方法

找到此电脑 右键设备管理器&#xff0c;找到其中的通用串行总线控制器。 注意&#xff0c;凡是插入到电脑当中不能读取的U盘或者移动硬盘&#xff0c;都会在通用串行总线控制器当中显示为USB大容量存储设备 鼠标选中“USB大容量存储设备”&#xff0c;右键卸载它。此时&#x…

【算法每日一练]-动态规划(保姆级教程 篇17 状态压缩)#POJ1185:炮兵阵地 #互不侵犯

目录 今日知识点&#xff1a; 把状态压缩成j,dp每行i的布置状态&#xff0c;从i-1和i-2行进行不断转移 把状态压缩成j,dp每行i的布置状态&#xff0c;从i-1行进行状态匹配&#xff0c;然后枚举国王数转移 POJ1185&#xff1a;炮兵阵地 思路&#xff1a; 题目&#xff1a;互…

Verilog刷题笔记44

题目&#xff1a;Consider the n-bit shift register circuit shown below: 解题&#xff1a; module top_module (input clk,input w, R, E, L,output Q );always(posedge clk)beginif(L1)Q<R;elseQ<(E1)?w:Q;endendmodule结果正确&#xff1a; 注意点&#xff1a; …

吴恩达2022机器学习专项课程(一) 3.6 可视化样例

问题预览 1.本节课主要讲的是什么&#xff1f; 2.不同的w和b&#xff0c;如何影响线性回归和等高线图&#xff1f; 3.一般用哪种方式&#xff0c;可以找到最佳的w和b&#xff1f; 解读 1.课程内容 设置不同的w和b&#xff0c;观察模型拟合数据&#xff0c;成本函数J的等高线…

安卓studio连接手机之后,一两秒之后就自动断开了。问题解决。

太坑了&#xff0c;安卓studio链接手机之后。几秒之后就断开了。我以为是adb的问题&#xff0c;就重新安装了一下adb。并且在环境变量中配置了Path的路径。然而并没有什么用啊。 经过排查原来是数据心虚了。线的接触不良。导致你刚接通的瞬间有相对较强的电流是因为有瞬间高电压…

【ArcGIS微课1000例】0106:ArcGIS制作风向频率(风速)玫瑰图

文章目录 一、效果预览二、加载数据三、创建图表四、图表修饰五、保存图片一、效果预览 在ArcGIS制作的风向频率玫瑰图最终效果如下所示: 二、加载数据 加载配套实验数据包中0106.rar中的excel数据,然后右键→打开。 三、创建图表 1. 创建图表。右击打开属性表,选择表选项…

Mac电脑高清媒体播放器:Movist Pro for mac下载

Movist Pro for mac是一款专为Mac操作系统设计的高清媒体播放器&#xff0c;支持多种常见的媒体格式&#xff0c;包括MKV、AVI、MP4等&#xff0c;能够流畅播放高清视频和音频文件。Movist Pro具有强大的解码能力和优化的渲染引擎&#xff0c;让您享受到更清晰、更流畅的观影体…

蓝桥杯第十五届抱佛脚(二)竞赛中的数据结构

蓝桥杯第十五届抱佛脚&#xff08;二&#xff09;内置数据结构 文章目录 蓝桥杯第十五届抱佛脚&#xff08;二&#xff09;内置数据结构在竞赛中常见的数据结构数组(Array)链表(Linked List)栈(Stack)队列(Queue)树(Tree)映射(Map) 内置数据结构的快速使用迭代器&#xff08;It…

(一)基于IDEA的JAVA基础7

关系运算符 运算符 含义 范例 结果 等于 12 false &#xff01; 不等于 1&#xff01;2 true > 大于 1>2 false < 小于 …

谈谈我对 AIGC 趋势下软件工程重塑的理解

作者&#xff1a;陈鑫 今天给大家带来的话题是 AIGC 趋势下的软件工程重塑。今天这个话题主要分为以下四大部分。 第一部分是 AI 是否已经成为软件研发的必选项&#xff1b;第二部分是 AI 对于软件研发的挑战及智能化机会&#xff0c;第三部分是企业落地软件研发智能化的策略…

Android 项目新建问题总结

title: Android 项目新建问题总结 search: 2024-03-24 tags: “#Android 项目新建问题总结” Android 项目新建问题总结 一、gradle 项目每次都自动下载依赖包到C盘 背景&#xff1a;idea 首次打开一个 gradle 项目&#xff0c;都会在 C 盘下载项目所需的依赖包&#xff0c;但…