一篇文章带你彻底搞懂十大经典排序之——快速排序

一、递归实现快速排序 

1.基本思想

通过一趟排序将待排序记录分割成独立的两部分,其中一部分记录的关键字均比两一部分的关键字小,则课分别对这两部分记录继续进行排序,已达到整个序列有序。

2.算法描述

快速排序使用分治法来吧一个“串”分成两个“子串”。集体算法描述如下:

(1) 从数组中跳出一个元素,称为“基准”(key)

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

(3) 递归,把小于key值元素的子数列和大于key值元素的子序列排列。

总结来说,该一趟该算法的效果 

(1) 确定key值的位置

(2) 分区间

3. hoare法(左右指针法)

hoare法是分区间的方法

(1)动图演示

(2) 具体步骤

● 先选定key值,一般选最左边或左右边,以key值在最左侧,且排升序为例

● 右指针 R 先向前移动,找比key小的值位置,找到之后,保持不动

● 左指针 L 再向后移动,找比key大的值的位置,找到之后,保持不动

● 交换 R 和 L 位置的数值

● 重复上述步骤23,知道 R 与 L 相遇,将相遇位置的值与key值进行交换(下面解释原因)

一趟排序结束,此时key左边的都是比key小的元素,右边都是比key大的元素

(3)相遇位置值与key交换

在上述 key值是最左边的值,且我们要得到的是升序数组的情况下,要将相遇位置的值与key值进行交换,说明:

  相遇位置的值一定比key小

证明: 

相遇的场景分析:

● L遇R :由于最左边是key,所以R先走,停下来(R停下的条件是遇到比key小的值)R停留的位置一定比key小,而L没有找到大的,遇到R才停下来。

● R遇L :R先走,找小于key的值,没有找到,直接与L相遇了。L停留的位置是上一轮交换的位置,上一轮交换,把比key小的值换到L的位置了。

相反:如果让最右边为key,则左边先走,可以保证相遇位置的值比key大 

(4)递归图解 

分区间:[ left , keyi - 1] keyi [ keyi + 1 , right ]

递归结束条件:

● 区间是一个值

● 不存在区间

(5)代码实现

void swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}//简单快排
void QuickSort(int* arr, int left, int right)
{if (left >= right)return;int keyi = left;int begin = left, end = right;while (begin < end){//先找end再找begin,顺序不能反//右边找小于arr[keyi]的数while (begin < end && arr[end] >= arr[keyi]){--end;}//左边找大于arr[keyi]的数while (begin < end && arr[begin] <= arr[keyi]){++begin;}swap(&arr[begin], &arr[end]);}//相遇位置的值一定比arr[keyi]小swap(&arr[keyi], &arr[begin]);keyi = begin;//划分区间,递归//[left,keyi - 1] keyi [keyi + 1,right]QuickSort(arr, left, keyi - 1);QuickSort(arr, keyi + 1, right);
}

 同样100万个随机数,不同排序的效率对比

(6)快排优化1(避免效率退化)

但该方法存在缺陷,当要排序的数组已经是有序的时候,调用该函数,会使递归的深度太深,有栈溢出的风险,而且效率也大大减小,直接由原来O(N*logN)的算法变成O(N^{2})的算法。

如图所示:

要避免有序情况下的效率退化:

1.随机选key

2.三数取中(更科学):取最左边、最右边和中间的数的做比较,选出中位数作key

注意:重新选出来的中位数下标所对应数组的值要与最左边的数进行交换,以不影响函数整体逻辑!

//避免有序情况下的的效率降低
//三数取中
int Middle(int* arr, int left, int right)
{int midi = (left + right) / 2;if (arr[midi] < arr[left]){if (arr[right] < arr[midi]){return midi;}else if (arr[right] < arr[left]){return right;}else{return left;}}if (arr[midi] > arr[left]){if (arr[right] > arr[midi]){return midi;}else if (arr[left] > arr[right]){return left;}else{return right;}}
}
//hoare法
int PartSort1(int* arr, int left, int right)
{//三数取中后得到的数,仍交换到最左边,为不影响整体逻辑int midi = Middle(arr, left, right);swap(&arr[midi], &arr[left]);int keyi = left;int begin = left, end = right;while (begin < end){//右边找小于arr[keyi]的数while (begin < end && arr[end] >= arr[keyi]){--end;}//左边找大于arr[keyi]的数while (begin < end && arr[begin] <= arr[keyi]){++begin;}swap(&arr[begin], &arr[end]);}//相遇位置的值一定比arr[keyi]小swap(&arr[keyi], &arr[begin]);return begin;
}
//简单快排
void QuickSort(int* arr, int left, int right)
{if (left >= right)return;int keyi = PartSort1(arr,left,right);//划分区间,递归//[left,keyi - 1] keyi [keyi + 1,right]QuickSort(arr, left, keyi - 1);QuickSort(arr, keyi + 1, right);
}

经过改良,同样对100万个随机数进行排序,快排的效率瞬间提升

(7)快排优化2(小区间优化)

经过上面的学习,我们知道快排是递归实现的,其过程近似于我们学过的二叉树,如果把函数递归的过程理想化想象成完全二叉树的递归过程,那么递归到最后一层的个数,相当于全部递归次数的一半,倒数第二层的递归数相当于全部的四分之一...如果后几层不进行递归排序,是不是可以大大提高排序的效率!

下图展示的是,不同层数组快排的次数:

解决方案:

小区间优化,小区间内不再递归分割排序而是进行插入排序,减少递归次数

我们规定一下小区间:

当(right- left + 1) < 10 时,[right,left]属于小区间

//避免有序情况下的的效率降低
//三数取中
int Middle(int* arr, int left, int right)
{int midi = (left + right) / 2;if (arr[midi] < arr[left]){if (arr[right] < arr[midi]){return midi;}else if (arr[right] < arr[left]){return right;}else{return left;}}if (arr[midi] > arr[left]){if (arr[right] > arr[midi]){return midi;}else if (arr[left] > arr[right]){return left;}else{return right;}}
}//插入排序
void InsertSort(int* arr, int n)
{for (int i = 0; i < n - 1; i++){//[0,end]是有序的,end + 1位置的值插入到[0,end],保持有序int end = i;int tmp = arr[end + 1];while (end >= 0){if (tmp < arr[end]){arr[end + 1] = arr[end];end--;}else{break;}}arr[end + 1] = tmp;}
}
//hoare法
int PartSort1(int* arr, int left, int right)
{//三数取中后得到的数,仍交换到最左边,为不影响整体逻辑int midi = Middle(arr, left, right);swap(&arr[midi], &arr[left]);int keyi = left;int begin = left, end = right;while (begin < end){//右边找小于arr[keyi]的数while (begin < end && arr[end] >= arr[keyi]){--end;}//左边找大于arr[keyi]的数while (begin < end && arr[begin] <= arr[keyi]){++begin;}swap(&arr[begin], &arr[end]);}//相遇位置的值一定比arr[keyi]小swap(&arr[keyi], &arr[begin]);return begin;
}//简单快排
void QuickSort(int* arr, int left, int right)
{if (left >= right)return;if ((right - left + 1) < 10){InsertSort(arr + left, right - left + 1);}else{int keyi = PartSort1(arr,left,right);//划分区间,递归//[left,keyi - 1] keyi [keyi + 1,right]QuickSort(arr, left, keyi - 1);QuickSort(arr, keyi + 1, right);}}

100万个随机数,不同排序的效率:

 

4.挖坑法

挖坑法是对hoare法的优化,没有效率提升,但是可以不用分析:

1.左边做key,右边先走的问题

2.相遇位置为什么比key小的问题,因为它的相遇位置是坑

1.动图演示 

2.具体步骤

● 先将第一个数据存放在临时变量key中,形成一个坑位

● 右指针R开始向前移动,找到比key值小的位置

● 找到后,将该位置的值放入坑位,该位置形成新的坑位

● 左指针L开始向后移动,找到比key值大的位置

● 找到后,将该位置的值放入坑位,该位置形成新的坑位

● 重复上述步骤2345,直到L与R相遇,最后将key的值放入相遇位置的坑位中

结束,此时坑位左边的值都比坑位的值小,右边的值都比坑位的值大。

3.递归图示 

4.代码实现 

将之前所提到过的优化方法一并带入:

//插入排序
void InsertSort(int* arr, int n)
{for (int i = 0; i < n - 1; i++){//[0,end]是有序的,end + 1位置的值插入到[0,end],保持有序int end = i;int tmp = arr[end + 1];while (end >= 0){if (tmp < arr[end]){arr[end + 1] = arr[end];end--;}else{break;}}arr[end + 1] = tmp;}
}//避免有序情况下的的效率降低
//三数取中
int Middle(int* arr, int left, int right)
{int midi = (left + right) / 2;if (arr[midi] < arr[left]){if (arr[right] < arr[midi]){return midi;}else if (arr[right] < arr[left]){return right;}else{return left;}}if (arr[midi] > arr[left]){if (arr[right] > arr[midi]){return midi;}else if (arr[left] > arr[right]){return left;}else{return right;}}
}//挖坑法
void QuickSort2(int* arr, int left, int right)
{if (left >= right)return;//小区间优化if ((right - left + 1) < 10){InsertSort(arr + left, right - left + 1);}else{//避免效率退化//三数取中后得到的数,仍交换到最左边,为不影响整体逻辑int midi = Middle(arr, left, right);swap(&arr[midi], &arr[left]);//key是临时变量,记录最左边的元素int key = arr[left];int begin = left, end = right;//pit是坑位int pit = left;while (begin < end){//右边找小于key的数while (begin < end && arr[end] >= key){--end;}//将找到的比key小的数填到坑位,刷新新坑位arr[pit] = arr[end];pit = end;//左边找大于arr[keyi]的数while (begin < end && arr[begin] <= key){++begin;}//将找到的比key大的数填到坑位,刷新新坑位arr[pit] = arr[begin];pit = begin;}//最后将key值填充到坑位arr[pit] = key;key = arr[begin];//划分区间,递归//[left,pit - 1] pit [pit + 1,right]QuickSort(arr, left, pit - 1);QuickSort(arr, pit + 1, right);}
}

5.前后指针法

(1)动图演示

(2)具体步骤

● 初始时,prev指针指向序列开头,cur指向prev指向的下一位

● 判断cur指针指向的数据是否小于key,若小于,则prev指针后移一位,并将cur与prev所指向的内容交换,然后cur++

● 若cur指向的数据大于key,cur++

● 重复步骤23,直到cur越界

 将prev所指向的值与key交换

此时key左边的数都比key小,右边的值都比key大

(3)代码实现

//插入排序
void InsertSort(int* arr, int n)
{for (int i = 0; i < n - 1; i++){//[0,end]是有序的,end + 1位置的值插入到[0,end],保持有序int end = i;int tmp = arr[end + 1];while (end >= 0){if (tmp < arr[end]){arr[end + 1] = arr[end];end--;}else{break;}}arr[end + 1] = tmp;}
}
//三数取中
int Middle(int* arr, int left, int right)
{int midi = (left + right) / 2;if (arr[midi] < arr[left]){if (arr[right] < arr[midi]){return midi;}else if (arr[right] < arr[left]){return right;}else{return left;}}if (arr[midi] > arr[left]){if (arr[right] > arr[midi]){return midi;}else if (arr[left] > arr[right]){return left;}else{return right;}}
}
//前后指针法
int PartSort2(int* arr, int left, int right)
{//三数取中后得到的数,仍交换到最左边,为不影响整体逻辑int midi = Middle(arr, left, right);swap(&arr[midi], &arr[left]);int keyi = left;int prev = left, cur = prev + 1;while (cur <= right){//cur找小,与prev位置的值交换if (arr[cur] < arr[keyi] && ++prev != cur)swap(&arr[cur], &arr[prev]);//找不到就向后移一位cur++;}swap(&arr[keyi], &arr[prev]);return prev;
}
//快排
void QuickSort(int* arr, int left, int right)
{if (left >= right)return;if ((right - left + 1) < 10){InsertSort(arr + left, right - left + 1);}else{int keyi = PartSort2(arr, left, right);//划分区间,递归//[left,keyi - 1] keyi [keyi + 1,right]QuickSort(arr, left, keyi - 1);QuickSort(arr, keyi + 1, right);}
}

二、非递归实现快排

数据结构的栈是在堆上开辟的空间,比函数调用所开辟的栈大很多。

 具体方法如图所示:

#include"Stack.h"
//快排(用栈非递归)— dfs
void QuickSortNonR(int* arr, int left, int right)
{Stack st;StackInit(&st);//右左区间入栈StackPush(&st, right);StackPush(&st, left);//循环每走一次(相当于之前的一次递归),取栈顶区间,单趟排序,右左子区间入栈while (!StackEmpty(&st)){int begin = StackTop(&st);StackPop(&st);int end = StackTop(&st);StackPop(&st);int keyi = PartSort2(arr, begin, end);//[begin,keyi - 1] keyi [keyi + 1,end]//先右后左if (end > keyi + 1){StackPush(&st, end);StackPush(&st, keyi + 1);}if (begin < keyi - 1){StackPush(&st, keyi - 1);StackPush(&st, begin);}}StackDestroy(&st);
}

三、注意

面试时手撕,不用三数取中和小区间优化,讲一下思路就行

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

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

相关文章

QT中利用qss来创建一个圆角矩形窗口,并利用Qt::WA_TranslucentBackground属性解决留白问题

1、效果 2、实现 QWidget#centralwidget {border-radius: 30px solid default;border-image: url(:/images/bk<

探索认知智能的未来:知识图谱的崛起

知识图谱点燃语言模型的潜能 ©作者| 潇潇 来源|神州问学 一、 人工智能的三个层次 在人工智能的发展历程中&#xff0c;我们见证了从简单计算到复杂认知的飞跃。人工智能的发展可以概括为三个主要层次&#xff1a;计算智能、感知智能和认知智能。这三个层次不仅代表了技…

支持向量回归原理详解及Python代码示例

支持向量回归原理详解 支持向量回归&#xff08;Support Vector Regression, SVR&#xff09;是支持向量机&#xff08;SVM&#xff09;的一种扩展&#xff0c;用于回归问题。SVR通过寻找一个最佳的回归超平面&#xff0c;使得尽可能多的数据点落在超平面附近的ε-管内&#xf…

eNSP中VRRP的配置和使用

一、基础配置 1.新建拓扑图 2.配置vlan a.CORE-S1 <Huawei>system-view [Huawei]sysname CORE-S1 [CORE-S1]vlan 10 [CORE-S1-vlan10]vlan 20 [CORE-S1-vlan20]vlan 30 b.CORE-S2 <Huawei>system-view [Huawei]sysname CORE-S2 [CORE-S2]vlan 10 [CORE…

240627_图像24位深度(RGB图)转为8位深度(单通道图)

240627_图像24位深度&#xff08;RGB图&#xff09;转为8位深度&#xff08;单通道图&#xff09; 在使用网络上下载下来的一部分图像分割数据集时&#xff0c;有些标签图你看着是一个黑白图&#xff0c;但是他还是有可能是一张RGB三通道图&#xff0c;具体怎么区分呢。右击图…

FPGA - 图像灰度化

一&#xff0c;灰度图像概念 灰度数字图像是每个像素只有一个采样颜色的图像。这类图像通常显示为从最暗黑色到最亮的白色的灰度&#xff0c;尽管理论上这个采样可以任何颜色的不同深浅&#xff0c;甚至可以是不同亮度上的不同颜色。灰度图像与黑白图像不同&#xff0c;在计算机…

如何预防和处理他人盗用IP地址?

IP地址的定义及作用 解释 IP 地址在互联网中的作用。它是唯一标识网络设备的数字地址&#xff0c;类似于物理世界中的邮政地址。 1、IP地址盗窃的定义 解释一下什么是IP地址盗用&#xff0c;即非法使用他人的IP地址或者伪造IP地址的行为&#xff0c;这种行为可能引发法律和安…

hadoop离线与实时的电影推荐系统-计算机毕业设计源码10338

摘 要 随着互联网与移动互联网迅速普及&#xff0c;网络上的电影娱乐信息数量相当庞大&#xff0c;人们对获取感兴趣的电影娱乐信息的需求越来越大,个性化的离线与实时的电影推荐系统 成为一个热门。然而电影信息的表示相当复杂&#xff0c;己有的相似度计算方法与推荐算法都各…

02逻辑代数与硬件描述语言基础

2.1 逻辑代数&#xff08;简单逻辑的运算&#xff09; 2.2 逻辑函数的卡诺图&#xff08;从图论的角度&#xff09;化简法 2.3 硬件描述语言Verilog HDL基础&#xff08;研究生阶段才用得到&#xff09; 要求&#xff1a; 1、熟悉逻辑代数常用基本定律、恒等式和规则。 2、掌握…

武汉高校大学智能制造实验室数字孪生可视化系统平台建设项目验收

随着科技的不断进步和工业4.0的深入推进&#xff0c;智能制造已成为现代工业发展的重要方向。在这一背景下&#xff0c;武汉高校大学智能制造实验室积极响应国家号召&#xff0c;致力于将先进的数字孪生技术与智能制造教育相结合&#xff0c;打造了一个集教学、实训、科研于一体…

【JavaScript】事件绑定

目录 一、什么是事件 二、常见事件 2.1 鼠标事件 2.2 键盘事件 2.3 表单事件 2.4 页面加载事件 三、浏览器弹窗三种方式 四、事件绑定函数方式 五、事件触发方式 一、什么是事件 事件就是行为动作。在HTML中事件可以是浏览器的行为&#xff0c;也可以是用户的行为。当这些行为发…

基于esp-idf的arm2d移植

什么是ARM2D Arm在Github上发布了一个专门针对“全体” Cortex-M处理器的2D图形加速库——Arm-2D 我们可以简单的把这个2D图形加速库理解为是一个专门针对Cortex-M处理器的标准“显卡驱动”。虽然这里的“显卡驱动”只是一个夸张的说法——似乎没有哪个Cortex-M处理器“配得上…

丝杆支撑座:滚珠丝杆稳定运行的守护者!

丝杆支撑座是丝杆和电机之间连接的重要组成部分&#xff0c;发挥着非常重要的功能。提到丝杆支撑座和滚珠丝杆&#xff0c;很多人都会想到支撑关系&#xff0c;但丝杆支撑座作为滚珠丝杆系统中至关重要的角色&#xff0c;其作用远不止于简单的支撑。 丝杆支撑座安装过程非常简单…

绘唐3是免费的吗?

绘唐科技是一家中国电子信息产品制造商和供应商&#xff0c;成立于2005年。公司主要经营智能硬件、智能穿戴设备、智能家居设备和智能交通设备等领域的产品开发和销售。绘唐科技拥有强大的研发团队和制造能力&#xff0c;能够为客户提供定制化的产品解决方案。 绘唐科技的产品种…

CS-隐藏防朔源-数据转发-iptables(Linux自带的防火墙)

免责声明:本文仅做技术交流与学习... 目录 准备环境: 1-iptables转发机设置转发: 2-CS服务器配置iptables服务器的IP 准备环境: 两台外网服务器. --iptables服务器就是做一个中转...封了中转就没了... 1-iptables转发机设置转发: iptables -I INPUT -p tcp -m tcp --dport 8…

ACC:Automatic ECN Tuning for High-Speed Datacenter Networks 相关知识点介绍(一)

目录 ACC&#xff08;Adaptive Congestion Control&#xff09; 总结 结合 ACC 和 ECN ECN ECN&#xff08;Explicit Congestion Notification&#xff09; 静态 ECN 动态 ECN 对比 总结 FCT——flow completion time 具体解释 小鼠流和大象流 小鼠流&#xff08;…

【最新综述】基于伪标签的半监督语义分割

Semi-Supervised Semantic Segmentation Based on Pseudo-Labels: A Survey 摘要&#xff1a; 语义分割是计算机视觉领域的一个重要而热门的研究领域&#xff0c;其重点是根据图像中像素的语义对其进行分类。然而&#xff0c;有监督的深度学习需要大量数据来训练模型&#xff…

GPT-5的到来:智能飞跃与未来畅想

IT之家6月22日消息&#xff0c;在美国达特茅斯工程学院的采访中&#xff0c;OpenAI首席技术官米拉穆拉蒂确认了GPT-5的发布计划&#xff0c;预计将在一年半后推出。穆拉蒂形象地将GPT-4到GPT-5的飞跃比作高中生到博士生的成长。这一飞跃将给我们带来哪些变化&#xff1f;GPT-5的…

电路笔记(电源模块):TPS82130降压模块

芯片引脚说明 Layer 1 1 2 3 4 5 6 7 8 SS/TR PG FB VOUT EN VIN GND VOUT Thermal Pad 使能引脚&#xff0c;高电平启动。 反馈参考引脚。 连接到该引脚的外部电阻分压器对输出电压进行编程。 电源开漏输出引脚。 软启动和电压跟踪引脚。 上拉电阻可以连接到任何低于6V的电压。…

如何使用WxPusher向个人微信推送发送实时消息,比如定时任务等

wxpusher-sdk-java这个框架开源了&#xff1a;GitHub - wxpusher/wxpusher-sdk-java: 微信消息实时推送服务[WxPusher]的Java版本sdk&#xff0c;可以通过API实时给个人微信推送消息。wechat pusher. 文档地址&#xff1a;WxPusher微信推送服务 WxPusher (微信推送服务)是一个…