【排序算法】 快速排序(快排)!图解+实现详解!

在这里插入图片描述

🎥 屿小夏 : 个人主页
🔥个人专栏 : 算法—排序篇
🌄 莫道桑榆晚,为霞尚满天!

文章目录

  • 📑前言
  • 🌤️快速排序的概念
    • ☁️快速排序的由来
    • ☁️快速排序的思想
    • ☁️快速排序的实现步骤
  • 🌤️快速排序(递归版)
    • ☁️快排主框架
    • ☁️Hoare版本快排
      • ⭐代码与图解
      • ⭐代码解析:
    • ☁️挖坑法
      • ⭐代码与图解
      • ⭐代码解析:
    • ☁️双指针法
      • ⭐代码与图解
      • ⭐代码解析
    • ☁️三数取中优化
      • ⭐为什么要三数取中?
      • ⭐三数取中代码实现
    • ☁️小区间优化
      • ⭐什么是区间优化?
      • ⭐小区间优化代码实现
      • ⭐小区间优化的好处
  • 🌤️快速排序(非递归版)
    • ☁️代码解析
  • 🌤️快速排序的特性总结
  • 🌤️全篇总结

📑前言

什么是快排?快排的速度到底有多快呢?它们的思想和实现是什么样的?

本文会对这快速排序进行详解,绝对细致入微!让你彻底搞懂快排!

🌤️快速排序的概念

☁️快速排序的由来

英国计算机科学家Tony Hoare在1960年为了解决计算机上的排序问题,提出了快速排序的算法,最初是为了在英国的英尔兰电子公司(ELLIOTT Brothers)的快速硬件上实现高效的排序算法。

☁️快速排序的思想

快速排序的主要思想是分治法,将一个大问题分割成小问题,解决小问题后再合并它们的结果。

☁️快速排序的实现步骤

  1. 从待排序的数组中选择一个元素,称之为枢纽元(pivot)。
  2. 将数组中小于枢纽元的元素移到枢纽元的左边,将大于枢纽元的元素移到枢纽元的右边,这个过程称为分区(partition)。
  3. 递归地对枢纽元左边的子数组和右边的子数组进行排序。
  4. 当所有子数组都有序时,整个数组就自然有序了。

🌤️快速排序(递归版)

☁️快排主框架

void QuickSort(int* a, int left, int right)
{
// 假设按照升序对array数组中[left, right)区间中的元素进行排序if (right <= left)return;
// 按照基准值对array数组的 [left, right)区间中的元素进行划分//int keyi = PartSort1(a, left, right);//int keyi = PartSort2(a, left, right);int keyi = PartSort3(a, left, right);
// 划分成功后以div为边界形成了左右两部分 [left, keyi-1) 和 [keyi+1, right)
// 递归排[left, keyi-1)QuickSort(a, left, keyi - 1);
// 递归排[keyi+1, right)QuickSort(a, keyi + 1, right);
}

上述为快速排序递归实现的主框架,发现与二叉树前序遍历规则非常像,在写递归框架时想想二叉树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可。

☁️Hoare版本快排

⭐代码与图解

在这里插入图片描述

int PartSort1(int* a, int left, int right)
{//三数取中(优化)//int keyi = NumBers(a, left, right);//Swap(&a[keyi], &a[left]);int key = left;while (left < right){while (left < right && a[left] <= a[right]){right--;}while (left < right && a[left] <= a[right]){left++;}Swap(&a[left], &a[right]);}Swap(&a[left], &a[key]);return left;
}

⭐代码解析:

  1. 首先,定义一个变量key,用于保存基准值的下标,初始值为left。
  2. 进入一个循环,循环条件是left < right,即左右指针没有相遇。
  3. 在循环中,首先从右边开始,找到第一个小于等于基准值的元素的下标,将right指针左移,直到找到符合条件的元素或者left和right相遇。
  4. 然后从左边开始,找到第一个大于基准值的元素的下标,将left指针右移,直到找到符合条件的元素或者left和right相遇。
  5. 如果left < right,说明找到了需要交换的元素,将a[left]和a[right]交换位置。
  6. 重复步骤3到步骤5,直到left和right相遇。
  7. 最后,将基准值a[key]和a[left]交换位置,将基准值放在正确的位置上。
  8. 返回分割点的下标left。

实现了一次快速排序的分割操作,将数组分成两部分,左边的元素都小于等于基准值,右边的元素都大于基准值。然后再通过递归调用这个函数,这就是hoare版的快排。

☁️挖坑法

⭐代码与图解

在这里插入图片描述

int PartSort2(int* a, int left, int right)
{//三数取中优化//int keyi = NumBers(a, left, right);//Swap(&a[keyi], &a[left]);int key = a[left];int hole = left;//为第一个坑while (left < right){while (left < right && key <= a[right]){--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;
}

⭐代码解析:

  1. 定义一个变量key,用于保存基准值,初始值为a[left]。
  2. 定义一个变量hole,用于保存空洞的位置,初始值为left。
  3. 进入一个循环,循环条件是left < right,即左右指针没有相遇。
  4. 在循环中,首先从右边开始,找到第一个小于基准值的元素的下标,将right指针左移,直到找到符合条件的元素或者left和right相遇。
  5. 将a[right]的值赋给a[hole],将空洞的位置移动到right。
  6. 然后从左边开始,找到第一个大于基准值的元素的下标,将left指针右移,直到找到符合条件的元素或者left和right相遇。
  7. 将a[left]的值赋给a[hole],将空洞的位置移动到left。
  8. 重复步骤4到步骤7,直到left和right相遇。
  9. 最后,将基准值key放入空洞的位置a[hole],将基准值放在正确的位置上。
  10. 返回空洞的位置hole。

同样实现了将数据分成两部分,左边的元素都小于等于基准值,右边的元素都大于基准值。

☁️双指针法

⭐代码与图解

在这里插入图片描述

// 快速排序前后指针法
int PartSort3(int* a, int left, int right)
{//三数取中优化//int midi = NumBers(a, left, right);//Swap(&a[left], &a[midi]);int prev = left;int cur = prev + 1;int keyi = left;while (cur <= right){if (a[cur] < a[keyi] && ++prev != cur){Swap(&a[prev], &a[cur]);}++cur;}Swap(&a[prev], &a[keyi]);return prev;
}

⭐代码解析

  1. 定义两个指针prev和cur,分别指向left和left+1。
  2. 定义一个变量keyi,用于保存基准值的下标,初始值为left。
  3. 进入一个循环,循环条件是cur <= right,即cur指针没有越界。
  4. 在循环中,如果a[cur]小于基准值a[keyi],则将prev指针右移一位,并交换a[prev]和a[cur]的值,保证prev指针之前的元素都小于基准值。
  5. 将cur指针右移一位。
  6. 重复步骤4到步骤6,直到cur指针越界。
  7. 最后,将基准值a[keyi]和a[prev]交换位置,将基准值放在正确的位置上。
  8. 返回分割点的下标prev。

同样实现了将数据分成两部分,左边的元素都小于等于基准值,右边的元素都大于基准值。

☁️三数取中优化

⭐为什么要三数取中?

  1. 三数取中是为了选择一个更好的基准值,以提高快速排序的效率。在快速排序中,选择一个合适的基准值是非常重要的,它决定了每次分割的平衡性。

  2. 快速排序是通过一趟排序将待排序的数据分割成独立的两部分,其中一部分的所有数据都比另一部分的小,然后再对这两部分分别进行快速排序,递归地进行下去,直到整个序列有序。

  3. 如果每次选择的基准值都是最左边或最右边的元素,那么在某些情况下,快速排序的效率可能会降低。例如,当待排序序列已经有序时,如果每次选择的基准值都是最左边或最右边的元素,那么每次分割得到的两个子序列的长度差可能会非常大,导致递归深度增加,快速排序的效率降低。

  4. 而通过三数取中的优化,可以选择一个更好的基准值,使得每次分割得到的两个子序列的长度差更小,从而提高快速排序的效率。

  5. 具体来说,三数取中的优化是选择待排序序列的左端、右端和中间位置的三个元素,然后取它们的中值作为基准值。这样选择的基准值相对于最左边或最右边的元素,更接近整个序列的中间位置,可以更好地平衡分割后的两个子序列的长度,从而提高快速排序的效率。

  6. 通过三数取中的优化,可以减少递归深度,提高分割的平衡性,使得快速排序的效率更稳定,适用于各种不同的输入情况。

⭐三数取中代码实现

//三数取中
int NumBers(int* a, int left, int right)
{int mid = (left + right) / 2;// left mid rightif (a[left] < a[mid]){if (a[mid] < a[right]){return mid;}else if (a[left] > a[right])  // mid是最大值{return left;}else{return right;}}else // a[left] > a[mid]{if (a[mid] > a[right]){return mid;}else if (a[left] < a[right]) // mid是最小{return left;}else{return right;}}
}

☁️小区间优化

⭐什么是区间优化?

小区间优化是指在快速排序中,当待排序的子序列的长度小于一定阈值时,不再继续使用快速排序,而是转而使用直接插入排序。

⭐小区间优化代码实现

void QuickSort(int* a, int left, int right)
{if (right <= left)return;if(right - left + 1 > 10){int keyi = PartSort3(a, left, right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);}else{InsertSort(a + left,right - left + 1);}
}

⭐小区间优化的好处

  1. 减少递归深度:使用插入排序来处理较小的子序列,可以减少递归的深度,从而减少了函数调用的开销。
  2. 提高局部性:插入排序是一种稳定的排序算法,它具有良好的局部性,可以充分利用已经有序的部分序列。对于较小的子序列,插入排序的效率更高。
  3. 减少分割次数:对于较小的子序列,使用插入排序可以减少分割的次数。快速排序的分割操作需要移动元素,而插入排序只需要进行元素的比较和交换,因此在较小的子序列中使用插入排序可以减少分割操作的次数。

小区间优化可以在一定程度上提高快速排序的性能。它通过减少递归深度、提高局部性和减少分割次数来优化算法的效率,特别适用于处理较小的子序列。

🌤️快速排序(非递归版)

这里需要借助栈的来实现非递归.关于栈详情见:数据结构剖析–栈

// 快速排序 非递归实现
void QuickSortNonR(int* a, 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 = PartSort3(a, begin, end);if (keyi + 1 < end){StackPush(&st, end);StackPush(&st, keyi + 1);}if (begin < keyi - 1){StackPush(&st, keyi - 1);StackPush(&st, begin);}}StackDestroy(&st);
}

☁️代码解析

  1. 将整个序列的起始和结束位置入栈。然后,进入循环,不断从栈中取出子序列的起始和结束位置。
  2. 在每次循环中,通过PartSort3函数将当前子序列分割成两部分,并得到基准值的下标keyi。如果基准值右边的子序列长度大于1,则将右边子序列的起始和结束位置入栈。如果基准值左边的子序列长度大于1,则将左边子序列的起始和结束位置入栈。
  3. 循环继续,直到栈为空,表示所有的子序列都已经排序完成。

通过使用栈来模拟递归的过程,非递归实现避免了递归调用的开销,提高了快速排序的效率。

🌤️快速排序的特性总结

  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序

  2. 时间复杂度:O(N*logN)在这里插入图片描述

  3. 空间复杂度:O(logN)

  4. 稳定性:不稳定

🌤️全篇总结

​ 本章对快排从其思想到实现,一步步由浅入深的讲解,相信聪明的你看到这里已经对快排有一个明白的理解了!

看到这里希望给博主留个:👍点赞🌟收藏⭐️关注!

在这里插入图片描述

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

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

相关文章

开源 Wiki 软件 wiki.js

wiki.js简介 最强大、 可扩展的开源Wiki 软件。使用 Wiki.js 美观直观的界面让编写文档成为一种乐趣&#xff01;根据 AGPL-v3 许可证发布。 官方网站&#xff1a;https://js.wiki/ 项目地址&#xff1a;https://github.com/requarks/wiki 主要特性&#xff1a; 随处安装&a…

【Vue.js】Vue3全局配置Axios并解决跨域请求问题

系列文章目录 文章目录 系列文章目录背景一、部署Axios1. npm 安装 axios2. 创建 request.js&#xff0c;创建axios实例3. 在main.js中全局注册axios4. 在页面中使用axios 二、后端解决跨域请求问题方法一 解决单Contoller跨域访问方法二 全局解决跨域问题 背景 对于前后端分离…

Excel·VBA工作表导出为图片

《Excel转图片别再截图啦&#xff01;用这4个方法&#xff0c;高清且无损&#xff01;》&#xff0c;excel转为图片一般方法较为简单&#xff0c;那么能否使用vba将excel转为图片 选中区域导出为图片 zoom设置为2&#xff0c;导出图片较为清晰 Sub 选中区域导出为图片()Dim …

web3 React dapp中编写balance组件从redux取出并展示用户资产

好啊 上文WEB3 在 React搭建的Dapp中通过redux全局获取并存储用户ETH与自定义token与交易所存储数量中 我们拿到了用户的一个本身 和 交易所token数量 并放进了redux中做了一个全局管理 然后 我们继续 先 起来ganache的一个模拟环境 ganache -d然后 我们启动自己的项目 顺手发…

聚观早报 |小米CarWith启动兼容测试;「天工」大模型开放

【聚观365】11月6日消息 小米CarWith启动兼容测试 「天工」大模型开放 邮政快递揽收超20亿件 华为Mate 60 Pro开启预约申购 VERTU持续探索前沿科技 小米CarWith启动兼容测试 小米CarWith是打造“人车家生态”中不可或缺的一环&#xff0c;在最新升级的2.0版本中&#xff…

Jupyter notebook如何加载torch环境

默认你已经安装了anaconda 和 pytorch 环境。 1&#xff0c;必须要以管理员身份打开 Anaconda prompt终端&#xff0c; 2&#xff0c;进入pytorch环境中&#xff1a; conda activate pytorch_393&#xff0c;安装必要插件&#xff1a; &#xff08;1&#xff09;conda inst…

项目管理之如何识别并应对项目风险

项目风险管理是项目管理中不可忽视的环节&#xff0c;如何识别并应对项目的风险对于项目的成功实施至关重要。本文将介绍风险管理的流程、风险分解结构、定性及定量风险评估方法&#xff0c;以及消极和积极的风险应对策略&#xff0c;旨在帮助读者更好地理解和应对项目风险。 …

Gorm 中的迁移指南

探索使用 GORM 在 Go 中进行数据库迁移和模式更改的世界 在应用程序开发的不断变化的景观中&#xff0c;数据库模式更改是不可避免的。GORM&#xff0c;强大的 Go 对象关系映射库&#xff0c;通过迁移提供了一种无缝的解决方案来管理这些变化。本文将作为您全面的指南&#xf…

最新知识付费变现小程序源码/独立后台知识付费小程序源码/修复登录接口

最新知识付费变现小程序源码&#xff0c;独立后台知识付费小程序源码&#xff0c;最新版修复登录接口。 主要功能 会员系统&#xff0c;用户登录/注册购买记录 收藏记录 基本设置 后台控制导航颜色 字体颜色 标题等设置 流量主广告开关小程序广告显示隐藏 广告主审核过审核…

Openssl生成证书-nginx使用ssl

Openssl生成证书并用nginx使用 安装openssl yum install openssl -y创库目录存放证书 mkdir /etc/nginx/cert cd /etc/nginx/cert配置本地解析 cat >>/etc/hosts << EOF 10.10.10.21 kubernetes-master.com EOF10.10.10.21 主机ip、 kubernetes-master.com 本…

使用 Python 进行自然语言处理第 4 部分:文本表示

一、说明 本文是在 2023 年 3 月为 WomenWhoCode 数据科学跟踪活动发表的系列文章中。早期的文章位于&#xff1a;第 1 部分&#xff08;涵盖 NLP 简介&#xff09;、第 2 部分&#xff08;涵盖 NLTK 和 SpaCy 库&#xff09;、第 2 部分&#xff08;涵盖NLTK和SpaCy库&#xf…

spring-boot中实现分片上传文件

一、上传文件基本实现 1、前端效果图展示&#xff0c;这里使用element-ui plus来展示样式效果 2、基础代码如下 <template><div><el-uploadref"uploadRef"class"upload-demo":limit"1":on-change"handleExceed":auto-…

JavaEE-博客系统3(功能设计)

本部分内容为&#xff1a;实现登录功能&#xff1b;强制要求用户登录&#xff1b;实现显示用户信息&#xff1b;退出登录&#xff1b;发布博客 该部分的后端代码如下&#xff1a; Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws Ser…

mysql 中!= 到底走不走索引?

mysql 中! 到底走不走索引&#xff1f; 很多人疑惑! 到底走不走索引&#xff0c; 这里可以肯定的说该操作是可以走索引的&#xff0c;但实际情况中都为啥都不走索引呢&#xff1f; 首先我们要知道走索引与数据量和数据趋势&#xff08;cardinality&#xff09;有很大的关系&…

bug: https://aip.baidubce.com/oauth/2.0/token报错blocked by CORS policy

还是跟以前一样&#xff0c;我们先看报错点&#xff1a;&#xff08;注意小编这里是H5解决跨域的&#xff0c;不过解决跨域的原理都差不多&#xff09; Access to XMLHttpRequest at https://aip.baidubce.com/oauth/2.0/token from origin http://localhost:8000 has been blo…

设计模式之装饰模式

一、概念 装饰模式是一种结构型设计模式&#xff0c;允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。 二、构造 部件&#xff08;Component&#xff09;声明封装器和被封装对象的公用接口。 具体部件&#xff08;Concrete Component&#xff09;类是…

随机森林在生物信息中的应用

今天与大家分享一项强大的机器学习算法随机森林。这个算法不仅在数据科学领域广泛应用&#xff0c;还在生物信息学中发挥了巨大的作用。 让我们一起探索随机森林的原理、优缺点以及它在生物信息领域的实际应用场景&#xff0c;本文将给出R语言进行应用的实际方法&#xff0c;利…

一个可以自动把微信聊天收到的二维码图片实时提取出来并分类的软件

10-1 如果你有需要实时地、自动地把微信各个群收到的二维码图片提取出来的需求&#xff0c;那本文章适合你&#xff0c;本文章的主要内容是教你如何实现自动提取微信收到的二维码图片&#xff0c;助你快速扫码&#xff0c;永远比别人领先一步。 首先需要准备好的材料&#xf…

SpringBoot + Vue2项目打包部署到服务器后,使用Nginx配置SSL证书,配置访问HTTP协议转HTTPS协议

配置nginx.conf文件&#xff0c;这个文件一般在/etc/nginx/...中&#xff0c;由于每个人的体质不一样&#xff0c;也有可能在别的路径里&#xff0c;自己找找... # 配置工作进程的最大连接数 events {worker_connections 1024; }# 配置HTTP服务 http {# 导入mime.types配置文件…

信驰达RF-DG-52PAS Zigbee 3.0协调器Home Assistant上手指南

一、使用前准备 RF-DG-52PAS是信驰达科技基于美国 TI CC2652P和CP2102为核心设计的Zigbee3.0 USB Dongle,可烧录 Z-Stack 3.x.0协调器固件&#xff0c;可以直接连接到计算机或树莓派&#xff0c;通过ZHA或 Zigbee2MQTT连接到 Home Assistant或其他开源物联网平台。还可以烧录…