快速排序(Quick Sort)算法详解(递归与非递归)

article/2025/6/7 23:23:27

引言

在计算机科学中,排序算法是最基础且重要的算法之一。快速排序(Quick Sort)作为一种高效的排序算法,在实际应用中被广泛使用。平均时间复杂度为 (O(n log n)),最坏情况下为 (O(n^2))。本文将详细介绍快速排序算法的原理,结合具体代码进行分析,给出两种递归方法与一种非递归方法,并给出两种优化方案。

快速排序原理

快速排序的基本思想是通过选择一个基准元素(key),将数组分为两部分,使得左边部分的所有元素都小于等于基准元素,右边部分的所有元素都大于等于基准元素。然后递归地对左右两部分进行排序,最终得到一个有序的数组。

具体步骤如下:

  1. 选择基准元素:从数组中选择一个元素作为基准元素。
  2. 分区操作:将数组中的元素重新排列,使得所有小于等于基准元素的元素都在基准元素的左边,所有大于等于基准元素的元素都在基准元素的右边。这个过程称为分区(partition)。
  3. 递归排序:对左右两部分分别递归地应用快速排序算法。

代码实现

1.hoare法(递归)

/交换
void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}
//快速排序
void QuickSort(int* a, int left, int right)
{if(left >= right)return;int key = left;int begin = left, end = right;while(begin < end){while(begin < end && a[end] > a[key]){end--;}while(begin < end && a[begin] <= a[key]){begin++;}Swap(&a[begin], &a[end]);}Swap(&a[key], &a[begin]);key = begin;QuickSort(a, left, key - 1);QuickSort(a, key + 1, right);}
}

 2.双指针法(递归)

//快排递归:双指针
int PartSort2(int* a, int left, int right)
{//三数取中(优化)int mid = GetMid(a, left, right);Swap(&a[mid], &a[left]);int key = left;int prev = left;int cur = prev + 1;while(cur <= right){if(a[cur] < a[key] && ++prev != cur){Swap(&a[cur], &a[prev]);}cur++;}Swap(&a[prev], &a[key]);return prev;
}
//快速排序(递归)
void QuickSort(int* a, int left, int right)
{if(left >= right)return;//小区间优化if((right - left + 1) < 10){InsertSort(a + left, right - left + 1);}else{// int key = PartSort1(a, left, right);int key = PartSort2(a, left, right);QuickSort(a, left, key - 1);QuickSort(a, key + 1, right);}
}

代码解释:
  1. Swap 函数:用于交换两个整数的值。
  2. QuickSort 函数
    • 递归终止条件:当 left >= right 时,说明数组已经有序,直接返回。
    • 分区操作:使用双指针法将数组分为两部分,左边部分的元素都小于等于基准元素,右边部分的元素都大于等于基准元素。
    • 递归排序:对左右两部分分别递归地调用 QuickSort 函数。

3.非递归法

递归方法来实现排序终归有栈溢出的风险,所以这里提供一种非递归方式实现快速排序

//快速排序(非递归)
void QuickSortNonR(int* a, int left, int right)
{ST st;STInit(&st);STPush(&st, right);STPush(&st, left);while(!STEmpty(&st)){int begin = STTop(&st);STPop(&st);int end = STTop(&st);STPop(&st);int key = PartSort2(a, begin, end);if(end > key + 1){STPush(&st, end);STPush(&st, key + 1);}if(begin < key - 1){STPush(&st, key - 1);STPush(&st, begin);}}STDestory(&st);
}

这里使用栈来存储指针begin与end ,栈在堆中存储,能通过malloc不断申请内存空间,有效规避了栈溢出的风险。

测试代码

下面对快速排序做一个简单的测试:

void TestOP()
{srand((unsigned int)time(NULL));const int N = 100000;int* a1 = (int*)malloc(sizeof(int) * N);for(int i = 0; i < N; i++){a1[i] = rand();}int begin1 = clock();// 此处可调用 QuickSort 进行测试QuickSort(a1, 0, N - 1);int end1 = clock();printf("QuickSort:%d\n", end1 - begin1);
}int main()
{TestOP();return 0;
}

在这个测试文件中,我们生成了一个包含 100000 个随机整数的数组,并调用 QuickSort 函数对其进行排序,最后输出排序所需的时间。

复杂度分析

  • 时间复杂度:平均情况下为 (O(n log n)),最坏情况下为 (O(n^2))。
  • 空间复杂度:递归调用栈的深度为 (O(log n)),因此空间复杂度为 (O(log n))。

优化方案 

1.三数取中

若数组排序前就有序,时间复杂度为O(n^2),那么此时三数取中就是消除这种接近有序带来的大量重复计算的优化方法

代码实现

//三数取中
int GetMid(int* a, int left, int right)
{int mid = (left + right) / 2;// left midi rightif (a[left] < a[mid]){if (a[mid] < a[right]){return mid;}else if (a[left] < a[right]){return right;}else{return left;}}else // a[left] > a[mid]{if (a[mid] > a[right]){return mid;}else if (a[left] < a[right]){return left;}else{return right;}}
}

紧接着再把返回的mid值与left处值交换即可

//三数取中(优化)int mid = GetMid(a, left, right);Swap(&a[mid], &a[left]);

2.小区间优化

我们通过前面所学内容可以得知快排的递归是类似与二叉树递归的一种算法,那么高度越高所消耗的空间越大,每个递归函数的调用会建立一个函数栈帧,为了避免出现栈溢出的情况,我们可以采取小区间优化来规避风险并高效实现排序。

代码实现

(可任选一种时间复杂度为O(n^2)的排序,这里选择更为高效的插入排序。)

//插入排序 最好O(N) 最坏O(N ^ 2)
void InsertSort(int* a, int n)
{for(int i = 0; i < n - 1; i++){int end = i;int tmp = a[end + 1];while(end >= 0){if(tmp < a[end]){a[end + 1] = a[end];end--;}else{break;}}a[end + 1] = tmp;}
}
//小区间优化if((right - left + 1) < 10){InsertSort(a + left, right - left + 1);}

3.其他

除了这些优化方案,可能有人会觉得快速排序难以理解,这里还有一种较为通俗的挖坑法(并没有改变排序效率,只是更为通俗易懂),可以自行查阅资料。

总结

快速排序是一种高效的排序算法,通过分治法和分区操作,将数组不断地分为两部分进行排序。在实际应用中,通过三数取中和小区间优化,可以提高算法的性能。希望本文对你理解快速排序算法有所帮助。


http://www.hkcw.cn/article/ytgKLkYkYH.shtml

相关文章

8.RV1126-OPENCV 视频中添加LOGO

一.视频中添加 LOGO 图像大体流程 首先初始化VI,VENC模块并使能&#xff0c;然后创建两个线程&#xff1a;1.把LOGO灰度化&#xff0c;然后获取VI原始数据&#xff0c;其次把VI数据Mat化并创建一个感兴趣区域&#xff0c;最后把LOGO放感兴趣区域里并把数据发送给VENC。2.专门获…

Linux 下 ChromeDriver 安装

个人博客地址&#xff1a;Linux 下 ChromeDriver 安装 | 一张假钞的真实世界 Selenium 是一个用于 Web 应用程序测试的工具。可以通过它驱动浏览器执行特定的操作&#xff0c;如点击、下滑、资源加载与渲染等。该工具在爬虫开发中也非常有帮助。Selenium 需要通过浏览器驱动操…

C++学者给您讲数学之——数列

C学者为您解析数列基础 数列的概念 **数列&#xff08;sequence of number&#xff09;**是以正整数集&#xff08;或其有限子集&#xff09;为定义域的有序数集。数列中的每个数称为该数列的项&#xff0c;其中&#xff1a; 第一位称为第1项&#xff08;首项&#xff09; 第…

【Harmony OS】数据存储

目录 数据存储概述 首选项数据存储 关系型数据库 数据存储概述 • 数据存储 是为了解决应用数据持久化问题&#xff0c;使得数据能够存储在外存中&#xff0c;达到保存或共享目的。 • 鸿蒙应用数据存储包括 本地数据存储 和 分布式数据存储 。 • 本地数据存储 为应用…

程序员健康防护指南

深度学习欢迎访问&#xff1a;通义灵码2.5qwen3——节假日抢票不用愁&#xff0c;基于12306-MCP实现个人火车票智能查询小助手&#xff01;-CSDN博客 一、视觉系统防护工程 1. 数字眼疲劳综合征防控 蓝光管理&#xff1a;使用经认证的防蓝光眼镜可过滤45%有害蓝光&#xff0c;…

CSS 平铺+自动换行效果

先上效果图 样式 <template><div class"activity-questions"><h1>活动题库</h1><div v-if"loading" class"loading">加载中...</div><div v-else><div v-if"questions.length 0" clas…

苏超火了 “苏大强”的作业怎么抄 全网热潮背后的足球盛宴

“比赛第一,友谊第十四”是这里的原则。近日,江苏省首届城市足球联赛“苏超”火出圈。“苏超”由江苏省体育局与江苏省各设区市政府联合主办,13个设区市各派一队参加。联赛打破了准入的边界,队伍中既有职业球员也有个体工商户、大学生和高中生等业余球员。尽管球员水平与中…

ck-editor5的研究 (7):自定义配置 CKeditor5 的 toolbar 工具栏

文章目录 一、前言二、实现步骤1. 第一步: 搭建目录结构2. 第二步:配置toolbar工具栏的步骤(2-1). 配置粗体和斜体(2-2). 配置链接和标题+正文(2-3). 配置列表和引用(2-4). 配置自动格式化3. 第三步:更多工具三、测试效果和细节四、总结一、前言 在前面的文章中,我们已经对…

Skydel25.4发布:解锁自定义星座,增强C波段与干扰模拟能力

在GNSS模拟技术持续迭代的浪潮中&#xff0c;Skydel迈出创新一步&#xff0c;正式发布25.4.0版本及后续修复版本25.4.1。本次更新的核心突破在于引入了强大的自定义星座功能&#xff0c;赋予用户前所未有的自由度&#xff0c;可创建包含多达400颗卫星的专属星座&#xff0c;突破…

迅为RK3588开发板RKLLM-Toolkit 环境搭建安装 Miniconda

Conda 是一个开源的软件包管理系统和环境管理系统&#xff0c;它可以用于安装、管理和升级软件 包和依赖项&#xff0c;我们这里使用conda 的目的只是构建一个虚拟环境&#xff0c;所以选择轻量话的miniconda。 miniconda 的官方链接如下所示&#xff1a; 进入 miniconda 的…

Oracle双平面适用场景讨论会议

4月28日&#xff0c;我在杭州组织召开了Oracle双平面会议讨论沙龙。在国产化数据库浪潮的今天&#xff0c;Oracle数据库作为国产数据库的应急库&#xff0c;在国产数据库发生故障或者性能下降时&#xff0c;如何更好的使用Oracle。会议主题如下&#xff1a; 1、背景与痛点速览&…

tauri项目绕开plugin-shell直接调用可执行文件并携带任意参数

tauri项目的plugin-shell插件的要求太多了&#xff0c;用起来实在是不顺手&#xff0c;要求参数要求位置等&#xff0c;不行不行&#xff0c;客户要求可以在前端输入任意命令行参数并执行&#xff0c;哪怕是rm -rf都要无条件执行&#xff0c;好好好&#xff0c;满足你。 我们直…

AJ-Report

目录 AJ-Report是什么 CNVD-2024-15077(AJ-Report认证绕过和远程代码执行漏洞) AJ-Report是什么 AJ-Report是完全开源的BI(Business intelligence)平台&#xff0c;旨在帮助用户生成和管理各种类型的报表。它通常用于web应用中&#xff0c;用于分析和展示数据&#xff0c;常用于…

Rust 函数

文章目录 Rust 函数函数参数语句与表达式带返回值的函数代码示例 Rust 函数 函数 函数在 Rust 代码中非常常见。你已经见过了语言中最重要的函数之一&#xff1a;main 函数&#xff0c;它是许多程序的入口点。你还见过 fn 关键字&#xff0c;它允许你声明新的函数。 Rust 代码…

【Typst】3.Typst脚本语法

概述 Typst的核心就是它在标记语法的基础上提供了一个灵活强大的脚本语言&#xff0c;来支持复杂的排版操作。 本节就以一个脚本语言的角度&#xff0c;介绍一下Typst脚本的核心语法。 系列目录 1.Typst概述2.Typst标记语法和基础样式3.Typst脚本语法4.导入、包含和读取5.文…

Java 文件操作 和 IO(5)-- 综合案例练习 -- 示例三

文章目录 题目描述&#xff1a;扫描指定目录&#xff0c;并找到文件名称或文件内容中包含指定字符的所有普通文件&#xff08;不包含目录&#xff09;结果案例演示&#xff1a;设计思路&#xff1a;总体的思路&#xff1a;使用代码&#xff0c;分步实现1. 准备工作&#xff08;…

微深节能 筒仓卸料小车远程智能控制系统 格雷母线

微深节能筒仓卸料小车远程智能控制系统——格雷母线高精度定位解决方案 在现代化筒仓物料管理中&#xff0c;卸料小车的精准定位与远程控制是提升效率、保障安全的关键。武汉市微深节能科技有限公司推出的格雷母线高精度位移测量系统&#xff0c;为筒仓卸料小车提供远程智能控…

股票指数期货的变动与股票价格指数的关系是什么?

很多小伙伴刚接触金融投资的时候&#xff0c;常常会听到“股票指数期货”和“股票价格指数”这两个词&#xff0c;但搞不清楚它们之间的关系。今天&#xff0c;我就给大家讲讲&#xff0c;这两个东西到底是什么关系。 一、股票价格指数是个什么&#xff1f; 股票价格指数&…

2025LitCTF re wp复现

LitCTF2025 wp&&复现 easy_rc4 魔改RC4&#xff0c;直接在异或处下条件断点&#xff0c;动调获取密钥流 FeatureExtraction 定位到main 前面都是一些初始化函数以及把输入的char型字符串转成int型数据 关键加密在sub_401722(Block, des) 加密逻辑就是 unsigned in…