快速排序(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;
    // 创建哨兵 mid
    int mid = q[l];
    // 创建i,j指针进行移动
    int i = l, j = r;
    // 进行区间数字交换,使得左侧区间全小于等于mid,右侧区间全大于等于mid
    while (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.mfbz.cn/a/755440.html

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

相关文章

CSS|01 CSS简介CSS的3种书写方式注释

CSS简介 什么是CSS CSS&#xff08;Cascading Style Sheet&#xff09;&#xff0c;层叠样式表 或者 级联样式表&#xff0c;简称样式表。CSS的作用 主要用来给 HTML网页 设置外观或者样式。CSS的语法规则 h1 {属性:属性值}注意&#xff1a;1. CSS代码是由选择器和一对括号…

Ubuntu Server 和 Ubuntu Desktop 组合使用

1.常见的组合使用方式 Ubuntu Server 和 Ubuntu Desktop 确实可以组合使用&#xff0c;但具体要看你的需求和使用场景。以下是一些常见的组合使用方式&#xff1a; 单一设备上安装&#xff1a;你可以在一台设备上同时安装 Ubuntu Server 和 Ubuntu Desktop。这样&#xff0c;你…

【ONE·Linux || 高级IO(一)】

总言 主要内容&#xff1a;介绍五种IO模型的基本概念、学习IO多路转接&#xff08;select、poll编程模型&#xff09;。       文章目录 总言1、问题引入1.1、网络通信与IO1.2、五种IO模型1.2.1、举例引入1.2.2、IO模型具体含义介绍1.2.2.1、阻塞式IO1.2.2.2、非阻塞轮询检…

mathcup大数据竞赛论文中集成学习(或模型融合)的运用分析

ps: (模型融合和集成学习是两个紧密相关但又有所区别的概念。集成学习是一种更广泛的范式&#xff0c;而模型融合可以被视为集成学习的一种特殊形式或策略。) 1.集成学习原理 图1 如图1所示&#xff0c;集成学习是一种通过结合多个机器学习模型的预测来提高整体性能的策略。其…

数据结构-循环链表和双向链表

目录 前言一、循环链表1.1 循环链表的介绍1.2 循环链表的实现 二、双向链表总结 前言 本篇文章介绍数据结构中的循环链表和双向链表 一、循环链表 1.1 循环链表的介绍 将单链表的形式稍作改变&#xff0c;单链表的最后一个结点指向第一个结点 对第一个结点概念的说明&#…

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

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

致远互联FE协作办公平台 codeMoreWidget SQL注入致RCE漏洞复现

0x01 产品简介 致远互联FE协作办公平台是一款为企业提供全方位协同办公解决方案的产品。它集成了多个功能模块&#xff0c;旨在帮助企业实现高效的团队协作、信息共享和文档管理。 0x02 漏洞概述 致远互联FE协作办公平台 codeMoreWidget.jsp接口处存在SQL注入漏洞,未经授权攻…

有哪些防爬虫的方法

防爬虫的方法有robots.txt文、user-agent过滤、ip限制、验证码、动态页面生成、频率限制、动态url参数和反爬虫技术等。详细介绍&#xff1a;1、robots.txt文件&#xff0c;用于告诉搜索引擎爬虫哪些页面可以访问&#xff0c;哪些页面禁止访问&#xff1b;2、ip限制&#xff0c…

机器学习入门指南:理解基本概念与常见算法

目录 什么是机器学习&#xff1f; 机器学习的基本概念 1. 训练数据 2. 特征工程 3. 模型评估 监督学习与非监督学习的区别 监督学习 非监督学习 常见的机器学习算法 1. 线性回归与逻辑回归 2. 决策树与随机森林 3. 支持向量机&#xff08;SVM&#xff09; 4. K近邻…

2小时动手学习扩散模型(pytorch版)【入门版】【代码讲解】

2小时动手学习扩散模型&#xff08;pytorch版&#xff09; 课程地址 2小时动手学习扩散模型&#xff08;pytorch版&#xff09; 课程目标 给零基础同学快速了解扩散模型的核心模块&#xff0c;有个整体框架的理解。知道扩散模型的改进和设计的核心模块。 课程特色&#xf…

学生宿舍管理系统

摘 要 随着高校规模的不断扩大和学生人数的增加&#xff0c;学生宿舍管理成为高校日常管理工作中的重要组成部分。传统的学生宿舍管理方式往往依赖于纸质记录和人工管理&#xff0c;这种方式不仅效率低下&#xff0c;而且容易出错&#xff0c;无法满足现代高校管理的需求。因此…

不同node版本的切换及其指定版本vue-cli脚手架下载

目录 一.清空本地已安装node.js版本 二.装nvm管理工具 三.安装指定node版本 四.使用nvm命令切换或删除指定node版本 五.在指定node版本下下载指定vue-cli脚手架 一.清空本地已安装node.js版本 1.按健winR弹出窗口&#xff0c;键盘输入cmd&#xff0c;然后敲回车。 2.输入…

这是我见过的大模型 RAG 优化方案与实践最全总结了

暑期实习基本结束了&#xff0c;校招即将开启。 不同以往的是&#xff0c;当前职场环境已不再是那个双向奔赴时代了。求职者在变多&#xff0c;HC 在变少&#xff0c;岗位要求还更高了。提前准备才是完全之策。 最近&#xff0c;我们又陆续整理了很多大厂的面试题&#xff0c…

QT基本对话框(基本对话框、工具盒类、进度条、调色板与电子钟、可扩展对话框、程序启动画面)

此篇文章通过实例介绍基本对话框的用法。首先介绍标准文件对话框&#xff08;QFileDialog&#xff09;、标准颜色对话框&#xff08;QColorDialog&#xff09;、标准字体对话框&#xff08;QFontDialog&#xff09;、标准输入对话框&#xff08;QInputDialog&#xff09;以及标…

VMware ESXi 技术

目录 一、VMware ESXi安装 1. 在VMware WorkStation中创建一台虚拟机 2. 进入VMware ESXi控制台 3. 配置VMware ESXi网络 二、使用Web网页端登录管理ESXi 1. 分配许可证密钥&#xff08;选做&#xff09; 2. 管理ESXi 三、VMware ESXi控制台 1. 创建虚拟机 2. 定制虚拟…

laravel Dcat Admin 入门应用(七)列copyable和自定义copy

laravel Dcat Admin 入门应用&#xff08;七&#xff09;列copyable和自定义copy Dcat Admin 是一个基于 Laravel-admin 二次开发而成的后台构建工具&#xff0c;只需很少的代码即可构建出一个功能完善的高颜值后台系统。支持页面一键生成 CURD 代码&#xff0c;内置丰富的后台…

深入了解Qt 控件:Display Widgets部件(1) 以及 QT自定义控件(电池)

QT自定义控件(电池&#xff09; Chapter1 QT自定义控件(电池&#xff09;Chapter2 Qt教程 — 3.5 深入了解Qt 控件&#xff1a;Display Widgets部件(1)1 Display Widgets简介2 如何使用Display Widgets部件 Chapter3 Qt自定义控件电池组件使用前言一、最基本的使用方法二、Batt…

Navicat安装与连接教程

navicat 的安装 官网&#xff1a;https://www.navicat.com.cn/ 进入官网之后点击左上角的产品&#xff0c;然后往下滑动就可以看见许多类型&#xff0c;我们使用的是MongoDB数据库&#xff0c;所以就下载Navicat 17 for MongoDB 进入到这里之后&#xff0c;选择自己的系统版本…

基于Vue.js的电商前端模板:Vue-Dashboard-Template的设计与实现

摘要 随着电子商务的飞速发展&#xff0c;前端页面的设计和实现变得愈发重要。本文介绍了一个基于Vue.js的电商前端模板——Vue-Dashboard-Template&#xff0c;旨在提供一个高性能、易扩展的电商平台前端解决方案。该模板遵循响应式设计、模块化、组件化开发等设计原则&#…

使用python创建虚拟环境及exe打包

使用python创建虚拟环境及exe打包 使用python创建虚拟环境在虚拟环境使用PyQt进行exe封装 使用python创建虚拟环境 优点&#xff1a; &#xff08;1&#xff09;可以实现环境的即插即用&#xff08;2&#xff09;可以在使用pyqt打包时实现最小体积使用库——venv 进入你要创…