快速排序(C/C++实现)—— 简单易懂系列

前言

排序作用的重要性是不言而喻的,例如成绩的排名、预约时间的先后顺序、不同路程的消耗与利润等。快排可以实现O(n * logn)的时间复杂度,O(logn)的空间复杂度来实现排序【虽然结果是不稳定的】。

算法思想

快速排序实际上是采用分治的思想,每次迭代在当前区间中选取一个数作为哨兵,通过一系列的交换操作将当前区间分为左区间和右区间【使得左区间的值全部小于等于哨兵,右区间的值全部大于等于哨兵】。然后再对左区间、右区间执行这种划分区间的策略操作,当区间的长度为1时停止。等到所有分治的区间长度都为1时,此时的原数组就已经是一个排好序的数组了。

具体步骤

假设数组名称为q,具体步骤如下:

  1. 如果区间长度小于等于1了,则结束循环。否则执行下一步。
  2. 先从本区间中取出第一个数作为哨兵mid,即令mid等于本区间最左端元素的值。执行下一步。
  3. i等于本区间最左端元素在原数组中的下标j等于本区间最右端元素在原数组中的下标。执行下一步。
  4. 判断 i < j是否成立,如果满足,则执行下一步。否则跳转到第9点
  5. 判断q[j] >= mid && i < j是否成立,如果满足,则j向左移动一位【j--】,再次执行本轮【即本次步骤是循环】。否则执行下一步
  6. q[i] = q[j]。执行下一步。【目的:进行元素移动,保证右区间的值都是大于等于哨兵的值,此时j右侧的值都不小于哨兵的值,且一定会有下一步来使得q[j]的值大于等于哨兵的值】
  7. 判断q[i] <= mid && i < j是否成立,如果满足,则i向右移动一位【i++】,再次执行本轮【即本次步骤是循环】。否则执行下一步
  8. q[j] = q[i]。跳转到第4点【目的:进行元素移动,保证左区间的值都是小于等于哨兵的值,此时i左侧的值都不大于哨兵的值,且一定会有下一步来使得q[i]的值小于等于哨兵的值】【第4点~第8点是一轮大循环】
  9. q[i] = mid。执行下一步。【循环结束后,i的位置即是哨兵的位置,此时令q[i] = mid即可。这一步操作保证了第6、8点担忧的地方,即这里一定可以使得最终的q[i]、q[j]等于哨兵的值。
  10. 划分两个区间【本区间左端点,i - 1】,【i + 1, 本区间右端点】,将这两个区间再次执行第一步的操作。【整个步骤是快排的分治操作的循环】

图表演示

假设我们拥有一个数组:a,长度为:5,内容为:3 1 2 4 5,需要对其进行从小到大排序。则流程为:

第一次递归:

此时数组为:3 1 2 4 5
基础数据:

  • l = 0:本轮区间左边界在数组中的下标
  • r = 4:本轮区间右边界在数组中的下标
  • mid = a[l] = 3:哨兵的值
  • i = l = 0:左指针
  • j = r = 4:右指针

初始化数据:

31245
i、lj、r
哨兵【3】
  1. 此时q[j] = 5 > 哨兵,满足右指针移动条件,右指针左移。
31245
i、ljr
哨兵【3】
  1. 此时q[j] = 4 < 哨兵,满足右指针移动条件,右指针左移。
31245
i、ljr
哨兵【3】
  1. 此时q[j] = 2 > 哨兵,不满足右指针移动条件,进行元素移动【保证j右侧的值都大于哨兵的值】,接下来进行左指针移动
21245
i、ljr
哨兵【3】

此时q[l]的值不见了,但是!!!我们的哨兵存的就是q[l]的值,在最后q[l]的值会回到数组中,故一个元素的值都不会少。

  1. 此时q[i] = 2 < 哨兵,满足左指针移动条件,左指针右移。【第一次交换左右指针移动时左指针条件一定满足,因为此时q[i]的值是刚才q[j]的值,而刚才的q[j]是一定小于哨兵的值】
21245
lijr
哨兵【3】
  1. 此时i = j,循环条件结束,此时左右指针都不会再移动了,则执行q[i] = q[j]q[j] = q[i]是没有意义的,因为此时i = j
21245
li、jr
哨兵【3】
  1. 此时循环结束,令q[i] = mid
21345
li、jr
哨兵【3】

即此时q[l]的值回到数组中了,故数组中的一个元素的值都没有少。

  1. 划分两个新的区间l, i - 1i + 1, r。对这两个新区间进行递归处理。

第二轮递归

本轮的数组为:2 1【上一轮递归处理后得到的左区间】
基础数据:

  • l = 0:本轮区间左边界在数组中的下标
  • r = 1:本轮区间右边界在数组中的下标
  • mid = a[l] = 2:哨兵的值
  • i = l = 0:左指针
  • j = r = 1:右指针

初始化数据:

21
i、lj、r
哨兵【2】
  1. 此时q[j] = 1 < 哨兵,不满足右指针移动条件,进行元素移动【保证j右侧的值都大于哨兵的值】,接下来进行左指针移动。
11
i、lj、r
哨兵【2】
  1. 此时q[i] = 1 < 哨兵,满足左指针移动条件,左指针右移。【第一次交换左右指针移动时左指针条件一定满足,因为此时q[i]的值是刚才q[j]的值,而刚才的q[j]是一定小于哨兵的值】
11
li、j、r
哨兵【2】
  1. 此时i = j,循环条件结束,此时左右指针都不会再移动了,则执行q[i] = q[j]q[j] = q[i]是没有意义的,因为此时i = j
11
li、j、r
哨兵【2】
  1. 此时循环结束,令q[i] = mid
12
li、j、r
哨兵【2】
  1. 划分两个新的区间l, i - 1i + 1, r。对这两个新区间进行递归处理。

接下来迭代的流程同上,不再演示。

实现代码:

#include<stdio.h>
// 定义一个常量N,用来修饰数组的长度
#define N 100007
// 定义一个数组a,用来接受输入的数据
int a[N];
// 进行快速排序
void quickSort(int q[], int l, int r)
{// 当前区间的长度小于等于1时停止循环if (l >= r)  return;// 创建哨兵 midint mid = q[l];// 创建i,j指针进行移动int i = l, j = r;// 进行区间数字交换,使得左侧区间全小于等于mid,右侧区间全大于等于midwhile (i < j){// j指针从右向左移动,至到遇到第一个小于哨兵的值while (q[j] >= mid && i < j) j--;// 将该值移动到左区间中q[i] = q[j];// i指针从左向右移动,至到遇到第大个小于哨兵的值while (q[i] <= mid && i < j) i++;// 将该值移动到右区间中q[j] = q[i];}// 交换结束后此时i,j指针指向的同一个位置,即哨兵应该放的位置// 而左区间已经是全部小于等于哨兵的值,右区间已经是全部大于等于哨兵的值了。q[i] = mid;// 对划分出来的左右区间的再一次进行快排quickSort(q, l, i - 1);quickSort(q, i + 1, r);
}int main()
{int n; //要排序的数据量个数scanf("%d", &n);// 按顺序输入每一个数字for (int i = 0; i < n; i++){scanf("%d", &a[i]);}// 进行快速排序quickSort(a, 0, n - 1);// 按顺序输入排序后的数组内容for (int i = 0; i < n; i++){printf("%d ", a[i]);}return 0;
}

总结分析:

  1. 为什么是先移动j,而不是先移动i:因为哨兵等于q[i],那么先移动i,则此时q[r]的数据是没人保存的,如果发生交换了q[j] = q[i]之后,实际上q[r]的值就不见了。但如果先移动j,由于哨兵的值是mid,那么就算发生了交换q[i] = q[j],而q[l]的值还是存在的,即哨兵的值。
  2. 记住:哨兵存的数组中的值,而不是下标,他并不是一个抽象的内容,他实际上就是q[某个下标],而这个元素也会发生移动。即最开始哨兵的位置是在q[l],而最后哨兵已经被移动到q[i]了。
  3. 下一次迭代选中区间l, i - 1i + 1, r。不包含 i 是因为 i 这个位置已经是哨兵了,不需要再进行排序了,他的位置一定是这个地方。
  4. 初级版【或者说通用版本】的快速排序大部分情况下是可以使用的,但是效率并不能达到快速排序的预期值,比如该链接中的的题目是不能通过:活动 - AcWing 。需要将排序的步骤进行优化,才能达到真正快速排序的预期。【可参考下一篇链接】

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

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

相关文章

【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【15】异步_线程池

持续学习&持续更新中… 守破离 【雷丰阳-谷粒商城 】【分布式高级篇-微服务架构篇】【15】异步_线程池 初始化线程的 4 种方式开发中为什么使用线程池线程池七大参数线程池工作原理常见的 4 种线程池生产中如何使用线程池&#xff1f;CompletableFuture 异步编排—简介业务…

贰[2],WPF+HandyControl开发异常记录

开发环境 VS2022 WPF/.net6.0 HandyControl 1&#xff0c;异常1&#xff1a;Exe程序在锁屏后&#xff0c;再次进入&#xff0c;exe程序停止界面更新 经过一段地毯式搜索&#xff0c;发现是HandyControl:TabControl的样式导致&#xff0c;TabControlPlusBaseStyle&#xff0…

论软件系统架构风格

论软件系统架构风格 一、引言 在软件工程的广阔领域中&#xff0c;软件系统架构风格如同一座指引开发者在复杂系统中前行的灯塔。它不仅决定了软件系统的基本结构&#xff0c;还直接影响着系统的质量、可维护性和可扩展性。因此&#xff0c;对软件系统架构风格的研究和探索&a…

selenium4如何指定chrome和firefox的驱动(driver)路径

pythonpytestselenium框架的自动化测试脚本。 原本用的chrome&#xff0c;很久没用了&#xff0c;今天执行&#xff0c;发现chrome偷偷升级&#xff0c;我的chromedriver版本不对了。。。鉴于访问chrome相关网站太艰难&#xff0c;决定弃用chrome&#xff0c;改用firefox。因为…

2.SQL注入-字符型

SQL注入-字符型(get) 输入kobe查询出现id和邮箱 猜测语句,字符在数据库中需要用到单引号或者双引号 select 字段1,字段2 from 表名 where usernamekobe;在数据库中查询对应的kobe&#xff0c;根据上图对应上。 select id,email from member where usernamekobe;编写payload语…

JAVA期末速成库(10)第十一章

一、习题介绍 Check Point&#xff1a;P416 11.1&#xff0c;11.6&#xff0c;11.7&#xff0c;11.8&#xff0c;11.12&#xff0c;11.17&#xff0c;11.24 Programming Exercise&#xff1a;11.1 二、习题及答案 Check Point&#xff1a; 11.1 True or false? A subcl…

力扣爆刷第156天之TOP100五连刷46-50(字符串转整数、括号生成、两数相加)

力扣爆刷第156天之TOP100五连刷46-50&#xff08;字符串转整数、括号生成、两数相加&#xff09; 文章目录 力扣爆刷第156天之TOP100五连刷46-50&#xff08;字符串转整数、括号生成、两数相加&#xff09;一、8. 字符串转换整数 (atoi)二、22. 括号生成三、70. 爬楼梯四、2. 两…

CST--如何在PCB三维模型中自由创建离散端口

在使用CST电磁仿真软件进行PCB的三维建模时&#xff0c;经常会遇到不能自动创建离散端口的问题&#xff0c;原因有很多&#xff0c;比如&#xff1a;缺少元器件封装、开路端口、多端子模型等等&#xff0c;这个时候&#xff0c;很多人会选择手动进行端口创建&#xff0c;但是&a…

【redis】Redis AOF

1、AOF的基本概念 AOF持久化方式是通过保存Redis所执行的写命令来记录数据库状态的。AOF以日志的形式来记录每个写操作&#xff08;增量保存&#xff09;&#xff0c;将Redis执行过的所有写指令记录下来&#xff08;读操作不记录&#xff09;。AOF文件是一个只追加的文件&…

已解决javax.security.auth.login.LoginException:登录失败的正确解决方法,亲测有效!!!

已解决javax.security.auth.login.LoginException&#xff1a;登录失败的正确解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 目录 问题分析 出现问题的场景 报错原因 解决思路 解决方法 1. 检查用户名和密码 用户名和密码验证 2. 验证配置文件 …

使用System.getProperty获取系统属性

使用System.getProperty获取系统属性 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天我们将深入探讨在Java中如何使用System.getProperty方法获取系统属性&…

Spark基于DPU的Native引擎算子卸载方案

1.背景介绍 Apache Spark&#xff08;以下简称Spark&#xff09;是一个开源的分布式计算框架&#xff0c;由UC Berkeley AMP Lab开发&#xff0c;可用于批处理、交互式查询&#xff08;Spark SQL&#xff09;、实时流处理&#xff08;Spark Streaming&#xff09;、机器学习&a…

昇思25天学习打卡营第5天|MindSpore-ResNet50图像分类

MindSpore-ResNet50图像分类 CIFAR-10数据集 CIFAR-10数据集是一个广泛使用的图像分类数据集,它包含了60,000张32x32的RGB彩色图像,分为10个类别,每个类别有6,000张图像。这些类别包括飞机(airplane)、汽车(automobile)、鸟类(bird)、猫(cat)、鹿(deer)、狗(dog…

Echarts地图实现:山东省报考人数

Echarts地图实现&#xff1a;山东省报考人数 效果预览 设计思路 数据可视化&#xff1a;选择地图作为数据展示的方式&#xff0c;可以直观地展示山东省不同城市的报考人数分布。交互性&#xff1a;通过ECharts的交互功能&#xff0c;如提示框&#xff08;tooltip&#xff09;…

《晨集》开源软件平台的创新与发展

一、引言 在数字化浪潮的推动下&#xff0c;开源软件平台已成为推动软件创新、促进知识共享的重要力量。《晨集》作为新兴的开源软件平台&#xff0c;其上线标志着开源生态圈的又一重要里程碑。本文旨在探讨《晨集》开源软件平台的创新特点、对开发者社区的影响以及未来发展趋…

SQL | 主键(Primary Key)和外键(Foreign Key)有什么区别| 用一个简单例子说明

如是我闻&#xff1a; 主键&#xff08;Primary Key&#xff09;和外键&#xff08;Foreign Key&#xff09;是确保数据完整性的两个基本概念&#xff0c;下面我们尝试用一个形象的例子来理解这两个概念。 基本定义 主键&#xff08;Primary Key&#xff09; 一个主键是一个…

JavaWeb系列十七: jQuery选择器 上

jQuery选择器 jQuery基本选择器jquery层次选择器基础过滤选择器内容过滤选择器可见度过滤选择器 选择器是jQuery的核心, 在jQuery中, 对事件处理, 遍历 DOM和Ajax 操作都依赖于选择器jQuery选择器的优点 $(“#id”) 等价于 document.getElementById(“id”);$(“tagName”) 等价…

CMMI认证等级是如何划分的?

CMMI&#xff08;Capability Maturity Model Integration&#xff09;认证等级是根据组织在软件开发和维护过程中的成熟度水平进行划分的。它一共包含五个级别&#xff0c;每个级别都代表了不同的过程成熟度和组织能力。以下是CMMI认证等级的详细划分&#xff1a; 初始级&…

Linux 日志文件

Linux 日志文件 在 Linux 系统中&#xff0c;日志文件是记录系统和应用程序运行状态、错误信息、用户活动等重要数据的文件。通过分析日志文件&#xff0c;管理员可以监控系统的健康状况、诊断问题、追踪安全事件以及了解系统的使用模式。 常见的 Linux 日志文件 /var/log/mes…

Java中double类型数据进行运算的时候出现精度丢失问题

精度丢失通常发生在浮点数运算中。以下是几个常见的例子展示了浮点数精度丢失的现象&#xff1a; 示例 1: 简单加减法中的精度丢失 在某些情况下&#xff0c;浮点数的简单加减法会产生意想不到的结果。 public class PrecisionLossExample {public static void main(String…