快排(动图详细版,快速理解)

news/2024/5/5 15:55:05/文章来源:https://blog.csdn.net/weixin_73584362/article/details/130067367

注:本文主要介绍六大排序中的快排

在这里插入图片描述

文章目录

  • 前言
  • 一、三大法则
    • 1.1 Hoare法
    • 1.2 挖坑法
    • 1.3 双指针法(更加便捷)
    • 1.4 三种方法时间复杂度计算
  • 二、快排栈问题优化方式
    • 2.1 三数取中
    • 2.2 小区间优化
  • 三、非递归快排


前言

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


一、三大法则

1.1 Hoare法

什么是Hoare法则呢?
Hoare法是指在对数组进行排序时,定义两个变量,与一个在单子循环中不变的key值,右值先动找比key小的数字,找到后左值动,找比key大的数字,后进行交换以完成对数组的排序。

初始状态
初始状态

right先进行寻找,找到5<key3在这里插入图片描述

left进行寻找找到7>key,进行交换
哈哈哈

继续进行刚才的工作交换9/4
在这里插入图片描述

当左值与右值相等时,一次循环结束,left与right所在位置与初始的key位置进行Swap交换,进入递归循环。
在这里插入图片描述

1.Hoare法代码
void PartSort1(vector<int>& v, int begin, int end)
{//出递归判断if (begin >= end){return;}int key = begin;int left = begin, right = end;while (left < right){//寻找小于key的左值while (right > left && v[right] >= v[key]){right--;}//寻找大于key的右值while (right > left && v[left] <= v[key]){left++;}Swap(v[left], v[right]);}Swap(v[key], v[left]);//key左边都是小于key的数,key右边都是大于key的数字key = left;//进入递归,先递归左半部分小于key的区间,后递归右半部分大于key的区间QuickSort(v, begin, key - 1);QuickSort(v, key + 1, end);
}
int main()
{vector<int> v = { 8,12,5,3,6,4,7,91,5,16,35,21,52,2,1 };QuickSort(v, 0, v.size() - 1);PrintArray(v, v.size());return 0;
}

排序结果
![在这里插入图片描述](https://img-blog.csdnimg.cn/a5636b9a6b024053b2ed668c7b897e73.png

1.2 挖坑法

挖坑法原理与Hoare法一致,不过相较于Hoare法更易理解它遵循着两个原则
1.先将第一个数据存放在临时变量key中。
2.左边是坑的话,右边先走找小,找到后与左坑值交换,右位变成坑位。
3.左边再找大值,交换循环。

初始状态
在这里插入图片描述

right找到第一个小于key的值5,然后将5给与坑所在位置,将right位置为坑
在这里插入图片描述

left开始找大
在这里插入图片描述

left找到第一个大于key的数7,与坑置换,并将left位置设为坑
在这里插入图片描述

多次置换后left==right时,坑的左边都是小于key的数,坑的右边都是大于key1的数,将key赋值于坑所在位置,单词排序成功,进入递归
在这里插入图片描述

2.挖坑法代码
void QuickSort(vector<int>& v, int begin, int end)
{if (begin >= end){return;}int left = begin, right = end;int key = v[left], hole = left;while (left < right){//找右值while (left < right && v[right] >= key){--right;}//入坑,与坑的替换v[hole] = v[right];hole=right;while (left < right && v[left] <= key){++left;}v[hole] = key;hole = left;}v[hole] = key;//进入递归,先递归左半部分小于key的区间,后递归右半部分大于key的区间QuickSort(v, begin, hole - 1);QuickSort(v, hole + 1, end);
}
int main()
{vector<int> v = { 8,12,5,3,6,4,7,91,5,16,35,21,52,2,1 };QuickSort(v, 0, v.size() - 1);PrintArray(v, v.size());return 0;
}

排序结果与Hoare相同

1.3 双指针法(更加便捷)

1.cur找比key小的,找到后停下。
2.prev++,prev与cur所在交换。

初始状态,这边我定义cur从第二个位置开始,当然它也可以从第一个位置开始,具体可以在程序中改进
在这里插入图片描述

当prev与cur相同时不进行交换,继续便利寻找可交换的数字找到第一组7/3,进行交换
在这里插入图片描述

找到第二组交换数据9/4,进行交换
在这里插入图片描述

多次交换后我们可以发现cur走到了最后的位置,此时,一次循环结束,当然不要忘记将初始设置的key与最后prev位置的数字进行交换。我们就可以得到以prev为分界线的左边小,右边大的两个数据区间,进行递归排序
在这里插入图片描述

结果
在这里插入图片描述

3.双指针法代码
void QuickSort(vector<int>& v, int begin, int end)
{if (begin > end){return;}int prev = begin, cur = begin+1;int key = begin;while (cur <= end){	//当prev与cur相等时交换的写法/*if (v[cur] < v[key]){prev++;Swap(v[prev], v[cur]);}*///当prev与cur相等时直接跳过不交换的写法if (v[cur] < v[key] && ++prev != cur){Swap(v[prev], v[cur]);}cur++;}Swap(v[prev], v[key]);key = prev;QuickSort(v, begin, key - 1);QuickSort(v, key + 1, end);
}
int main()
{vector<int> v = { 8,12,5,3,6,4,7,91,5,16,35,21,52,2,1 };QuickSort(v, 0, v.size() - 1);PrintArray(v, v.size());return 0;
}

1.4 三种方法时间复杂度计算

这里较难描述,我直接给出答案以及答案出现的情乱
快排的时间复杂度最理想下可达到O(n*logn)
当一组数据趋近于有序(逆序、正序)时,时间复杂度可达O(n * n)
这是为什么呢?

话不多说直接上图
在这里插入图片描述
这张图是不是和二叉树很像,当每次key都能选到,中间值时,它的时间复杂度就是n*logn。

在这里插入图片描述
而当所排数组趋近于有序数组时,就会出现如上图所示的情况,而我们又知道,进行递归时,CPU需要不停的进行栈帧的开辟,如果遇到趋近于有序的数组时,时间复杂度为O(n * n),我们得不到想要的有序数组,还会使编译器崩溃,那它怎么能叫快排的,徒有其表吗。
接下来要讲两种规避这种情况的优化方式。
1.三数取中
2.小区间优化

二、快排栈问题优化方式

2.1 三数取中

三数取中法选key,可以解决上述最坏情况有序(顺序、逆序)的问题。
有效避免因为深度遍历而开过多栈,引起的栈溢出问题。
思维图:

初始状态
在这里插入图片描述

mid为中值
在这里插入图片描述

end为中值
在这里插入图片描述

begin为中值
在这里插入图片描述

//中值判断函数
int GetMidIndex(vector<int>& v, int begin, int end)
{int mid = (begin + end) / 2;if (v[begin] < v[mid]){if (v[mid] < v[end]){return mid;}else if (v[begin] > v[end]){return begin;}else{return end;}}else // v[begin] > v[mid]{if (v[mid] > v[end]){return mid;}else if (v[begin] < v[end]){return begin;}else{return end;}}
}
//加入中止判断的快排
void QuickSort(vector<int>& v, int begin, int end)
{if (begin > end){return;}//加入中值判断int mid = GetMidIndex(v, begin, end);Swap(v[begin], v[mid]);int prev = begin, cur = begin+1;int key = begin;while (cur <= end){/*if (v[cur] < v[key]){prev++;Swap(v[prev], v[cur]);}*/if (v[cur] < v[key] && ++prev != cur){Swap(v[prev], v[cur]);}cur++;}Swap(v[prev], v[key]);key = prev;QuickSort(v, begin, key - 1);QuickSort(v, key + 1, end);
}

2.2 小区间优化

小区间优化,字如其名,在进行递归排序时,会无限制的进行栈帧开辟,但是我们所用的内存空间的栈区往往最大就只有8M大小的空间,有概率会爆栈
经过多次排序后的数据在每个小区间会十分集中。
此时进行小区间的优化可以避免少开70%~90%的栈帧。
方法: 当小区间元素小于10/15时,使用直接插入排序进行排序。
思维图:

在所排数组非常大的情况下,如下图,会无线的开辟栈帧
在这里插入图片描述

适当的裁剪,换取更高的效率,减少百分之八十五左右的栈帧开辟
在这里插入图片描述

//直接插入排序
void InsertSort(vector<int>& v, int n)
{for (int i = 0; i < n - 1; ++i){int end = i;int tmp = v[end + 1];while (end >= 0){if (tmp < v[end]){v[end + 1] = v[end];--end;}else{break;}}v[end + 1] = tmp;}
}
//加入三数取中与小区间优化的快排
void QuickSort(vector<int>& v, int begin, int end)
{if (begin >= end){return;}//小区间优化,防止递归深度过大,进行的剪枝行为,后使用直接插入排序进行小范围排序if (end - begin + 1 < 10){InsertSort(v, end);}else{//三数取中int mid = GetMidIndex(v, begin, end);Swap(v[begin], v[mid]);int prev = begin, cur = begin + 1;int key = begin;while (cur <= end){/*if (v[cur] < v[key]){prev++;Swap(v[prev], v[cur]);}*/if (v[cur] < v[key] && ++prev != cur){Swap(v[prev], v[cur]);}cur++;}Swap(v[prev], v[key]);key = prev;QuickSort(v, begin, key - 1);QuickSort(v, key + 1, end);}
}

递归类快排,整体代码

#include <iostream>
#include <stack>
#include <vector>using namespace std;//交换
void Swap(int& a, int& b)
{int tmp = a;a = b;b = tmp;
}//打印
void PrintArray(vector<int> v,int n)
{for (int i = 0; i < n; ++i){cout << v[i] << " ";}printf("\n");
}//三数取中
int GetMidIndex(vector<int>& v, int begin, int end)
{int mid = (begin + end) / 2;if (v[begin] < v[mid]){if (v[mid] < v[end]){return mid;}else if (v[begin] > v[end]){return begin;}else{return end;}}else // v[begin] > v[mid]{if (v[mid] > v[end]){return mid;}else if (v[begin] < v[end]){return begin;}else{return end;}}
}//直接插入排序 O(n*n)
void InsertSort(vector<int>& v, int n)
{for (int i = 0; i < n - 1; ++i){int end = i;int tmp = v[end + 1];while (end >= 0){if (tmp < v[end]){v[end + 1] = v[end];--end;}else{break;}}v[end + 1] = tmp;}
}//一、Hoare法
void QuickSort(vector<int>& v, int begin, int end)
{if (begin >= end){return;}int key = begin;int left = begin, right = end;while (left < right){while (right > left && v[right] >= v[key]){right--;}while (right > left && v[left] <= v[key]){left++;}Swap(v[left], v[right]);}Swap(v[key], v[left]);key = left;QuickSort(v, begin, key - 1);QuickSort(v, key + 1, end);
}//二、挖坑法
//先将第一个数据存放在临时变量key中
//左边是坑的话,右边先走找小,找到后与左坑值交换,右位变成坑位
//左边再找大值,交换循环
void QuickSort(vector<int>& v, int begin, int end)
{if (begin >= end){return;}int mid = GetMidIndex(v, begin, end);Swap(v[begin], v[mid]);int left = begin, right = end;int key = v[left];int hole = left;while (left < right){while (left < right && v[right] >= key){--right;}v[hole] = v[right];hole=right;while (left < right && v[left] <= key){++left;}v[hole] = v[left];hole = left;}v[hole] = key;QuickSort(v, begin, hole - 1);QuickSort(v, hole + 1, end);
}//三、前后指针法
//1.cur找比key小的,找到后停下
//2.prev++,prev与cur所在交换
void QuickSort(vector<int>& v, int begin, int end)
{if (begin > end){return;}int prev = begin, cur = begin+1;int key = begin;while (cur <= end){/*if (v[cur] < v[key]){prev++;Swap(v[prev], v[cur]);}*/if (v[cur] < v[key] && ++prev != cur){Swap(v[prev], v[cur]);}cur++;}Swap(v[prev], v[key]);key = prev;QuickSort(v, begin, key - 1);QuickSort(v, key + 1, end);
}
//三数取中+小区间优化快排
void QuickSort(vector<int>& v, int begin, int end)
{if (begin >= end){return;}//小区间优化,防止递归深度过大,进行的剪枝行为,后使用直接插入排序进行小范围排序if (end - begin + 1 < 10){InsertSort(v, end);}else{//三数取中int mid = GetMidIndex(v, begin, end);Swap(v[begin], v[mid]);int prev = begin, cur = begin + 1;int key = begin;while (cur <= end){/*if (v[cur] < v[key]){prev++;Swap(v[prev], v[cur]);}*/if (v[cur] < v[key] && ++prev != cur){Swap(v[prev], v[cur]);}cur++;}Swap(v[prev], v[key]);key = prev;QuickSort(v, begin, key - 1);QuickSort(v, key + 1, end);}
}int main()
{vector<int> v = { 8,12,5,3,6,4,7,91,5,16,35,21,52,2,1 };QuickSort(v, 0, v.size() - 1);PrintArray(v, v.size());return 0;
}

三、非递归快排

非递归快排借助进行操作,是一种分治思维的延申。
因为栈的出入规则是先进后出。
我们要对排序区间进行划分,每次的出栈、入栈操作就是递归中处理数据、子区间划分操作。

原理图:

在这里插入图片描述
程序:

//非递归快排
void QuickSortNonr(vector<int>& v, int begin, int end)
{//自定义栈为ST,定义栈st,并初始化栈,将头尾位置压入栈//成功压入后我们得到需要的排序空间的头和尾//依次进行出栈入栈,重新定义排序区间,模拟递归排序ST st;StackInit(&st);StackPush(&st, begin);StackPush(&st, end);while (!StackEmpty(&st)){int right = StackTop(&st);StackPop(&st);int left = StackTop(&st);StackPop(&st);//调用单次快排,单次排序int key = QuickSort(a, left, right);// [left, keyi-1] keyi [keyi+1, right]if (key + 1 < right){//进行右区间入栈操作,进入模拟递归进入操作StackPush(&st, key + 1);StackPush(&st, right);}if (left < key - 1){//进行左区间入栈操作,进入模拟递归进入操作StackPush(&st, left);StackPush(&st, key - 1);}}//排序完毕销毁StackDestroy(&st);
}

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

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

相关文章

Linux高并发服务器(webserver)

一.有限状态机 它的转移函数表示系统从一个状态转移到另一个状态的条件 二.EPOLL 在内核中创建一个数据&#xff0c;这个数据有两个比较重要的数据&#xff0c;一个是需要检测的文件描述符的信息&#xff08;红黑树&#xff09;&#xff0c;一个双向链表&#xff0c;存放检测到…

利用多专家模型解决长尾识别任务

来源&#xff1a;投稿 作者&#xff1a;TransforMe 编辑&#xff1a;学姐 贡献 提出了RoutIng Diverse Experts&#xff08;RIDE&#xff09;&#xff0c;不仅可以减少所有类别的variance&#xff0c;并且还可以减少尾部类的bias。同时提升了头部和尾部的性能。 思路 目前存…

easyrecovery2023电脑文件数据恢复软件功能介绍

EasyRecovery功能全面&#xff0c;即便是没有经验的小白用户也可以很快上手&#xff0c;让你足不出户即可搞定常见的数据丢失问题。 在使用和操作存储设备期间&#xff0c;数据丢失问题在所难免。比如&#xff0c;误删除某个文件、不小心将有数据的分区格式化、误清空了有重要…

2023“认证杯”数学中国数学建模赛题浅析

2023年认证杯”数学中国数学建模如期开赛&#xff0c;本次比赛与妈杯&#xff0c;泰迪杯时间有点冲突。因此&#xff0c;个人精力有限&#xff0c;有些不可避免地错误欢迎大家指出。为了大家更方便的选题&#xff0c;我将为大家对四道题目进行简要的解析&#xff0c;以方便大家…

【vue3】04-vue基础语法补充及阶段案例

文章目录vue基础语法补充vue的computedvue的watch侦听书籍购物车案例vue基础语法补充 vue的computed computed&#xff1a;用于声明要在组件实例上暴露的计算属性。&#xff08;官方文档描述&#xff09; 我们已经知道&#xff0c;在模板中可以直接通过插值语法显示一些data中…

智能网卡相关知识(smart nic 、DPU)

网卡作为穿行在网络与计算之间的桥梁&#xff0c;是可以解决计算瓶颈的关键硬件。 随着CPU 密度和数据中心网络带宽的进一步提升&#xff0c;用户对预期性能的需求&#xff0c;系统运行平稳性都会有更高的要求。云厂商一方面面临巨大的成本压力&#xff0c;另一方面面临巨大的…

新一代异步IO框架 io_uring | 得物技术

1.Linux IO 模型分类 相比于kernel bypass 模式需要结合具体的硬件支撑来讲&#xff0c;native IO是日常工作中接触到比较多的一种&#xff0c;其中同步IO在较长一段时间内被广泛使用&#xff0c;通常我们接触到的IO操作主要分为网络IO和存储IO。在大流量高并发的今天&#xff…

光伏电池片技术N型迭代,机器视觉检测赋能完成产量“弯道超车”

电池片是光伏发电的核心部件&#xff0c;其技术路线和工艺水平直接影响光伏组件的发电效率和使用寿命。随着硅料、硅片技术逐渐接近其升级迭代空间的瓶颈&#xff0c;电池片环节正处于技术变革期&#xff0c;是光伏产业链中迭代最快的部分。P型中PERC电池片是现阶段市场的主流产…

C/C++每日一练(20230413)

目录 1. 与浮点数A最接近的分数B/C &#x1f31f; 2. 比较版本号 &#x1f31f;&#x1f31f; 3. 无重复字符的最长子串 &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每…

Multi-modal Alignment using Representation Codebook

Multi-modal Alignment using Representation Codebook 题目Multi-modal Alignment using Representation Codebook译题使用表示代码集的多模态对齐期刊/会议CVPR 摘要&#xff1a;对齐来自不同模态的信号是视觉语言表征学习&#xff08;representation learning&#xff09;的…

Vue2_02_指令

模板语法 — Vue.js (vuejs.org) 指令 (Directives) 是带有 v- 前缀的特殊 attribute 参数 一些指令能够接收一个“参数”&#xff0c;在指令名称之后以冒号表示 <a v-bind:href"url">...</a> 动态参数 可以用方括号括起来的 JavaScript 表达式作为一…

JWT与Token详解

前言&#xff1a;JWT全称“JSON Web Token”&#xff0c;是实现Token的机制。官网&#xff1a;https://jwt.io/ JWT的应用 JWT用于登录身份验证。用户登录成功后&#xff0c;后端通过JWT机制生成一个token&#xff0c;返回给客户端。客户端后续的每次请求都需要携带token&…

常用加密算法

目录 常见的加密算法可以分成三种&#xff1a; 对称加密算法 DES 3DES AES 非对称加密 RSA ECC Hash算法 MD5 SHA1 算法对比 算法选择 常见的加密算法可以分成三种&#xff1a; 对称加密算法&#xff1b;非对称加密算法&#xff1b;Hash算法&#xff1b;接下来我们…

面试手撕堆排序

堆排序代码如下&#xff08;注释见下&#xff09;&#xff1a; 首先将待排序的数组构造成一个大根堆&#xff0c;此时&#xff0c;整个数组的最大值就是堆结构的堆顶 将堆顶的数与末尾的数交换&#xff0c;此时&#xff0c;末尾的数为最大值&#xff0c;剩余待排序数组个数为n…

Linux设备驱动开发 - 块设备驱动ramdisk实例

By: fulinux E-mail: fulinuxsina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅&#xff01; 你的喜欢就是我写作的动力&#xff01; 目录理论来源源码编译测试理论来源 ramdisk驱动&#xff0c;区别在与使用最新的内核版本&#xff0c;比如linux-4.15。…

MySQL数据库:聚合函数、分组查询、约束、默认值设置、自增属性

一、聚合函数 1.聚合函数 在MySQL数据库中预定义好的一些数据统计函数。 2.count(*) 功能&#xff1a;统计结果条数。 3.sum(字段名) 功能&#xff1a;对指定字段的数据求和。 4.avg(字段名) 功能&#xff1a;对指定字段的数据求平均值。 5.max(字段名) 和 min(字段名) …

答疑——20年国赛题(JAVA解法)

题目链接&#xff1a;用户登录https://www.lanqiao.cn/problems/1025/learning/?page3&first_category_id1&sortstudents_count 题目描述 有 n 位同学同时找老师答疑。每位同学都预先估计了自己答疑的时间。 老师可以安排答疑的顺序&#xff0c;同学们要依次进入老…

sqoop数据导出、脚本使用

目录 准备表与数据 数据导出 脚本调用 准备表与数据 mysql表 CREATE TABLE user (id int(20),name varchar(20) )ENGINEINNODB DEFAULT CHARSETutf8; hive表 create table users( id bigint, name string ) row format delimited fields terminated by "\t";…

软考初级程序员--学习

1、十进制 转 二进制 十进制87 转换为 二进制为 1010111 2、二进制 转 十进制 二进制1010111 转换为 十进制 3、循环队列 计算长度通用公式&#xff1a; front&#xff1a;表示队首 rear&#xff1a;表示队尾 M&#xff1a;表示队列容量 队列长度 &#xff08;rear - fr…

Verilog | 二进制与格雷码

一、格雷码简介 格雷码是一个叫弗兰克格雷的人在 1953 年发明的&#xff0c;最初用于通信。格雷码是一种循环二进制码或者叫作反射二进制码。格雷码的特点是从一个数变为相邻的一个数时&#xff0c;只有一个数据位发生跳变&#xff0c;由于这种特点&#xff0c;就可以避免二进…