news 2026/9/7 14:39:33

快速排序 - 原理、时空分析、优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
快速排序 - 原理、时空分析、优化

过程

快速排序分为三个过程:

  1. 将数列根据划分值m mm划分为两部分;
  2. 递归到两个子序列中分别进行快速排序;
  3. 不用合并,因为此时数列已经完全有序。

具体来说,第一步要是要把数列分成两个部分,然后保证前一个子数列中的数都小于后一个子数列中的数。对于最优情况,每一次选择的分界值都是序列的中位数。

快速排序归并排序虽然都是划分区间,但是归并排序直接就可以选取中间位置,但快速排序需要选择的是中间数值,中间位置你是可以直接确定的,而中间数值你是无法确定的。为了保证平均时间复杂度,一般是随机选择一个数m mm来当做两个子数列的分界。

其实,快速排序没有指定应如何具体实现第一步,不论是选择m mm的过程还是划分的过程,都有不止一种实现方法。

第三步中的序列已经分别有序且第一个序列中的数都小于第二个数,所以直接拼接起来就好了。

在实现时,只需维护小于等于m mm的边界l o w lowlow,大于m mm的部分自然就唯一确定了。

朴素快速排序

publicstaticvoidquickSort(intl,intr){if(l>=r)// 只有一个数,或范围不存在return;intx=arr[l];// 固定方式选取划分值intmid=partition(l,r,x);// mid 位置已经固定// 处理剩余区间quickSort(l,mid-1);quickSort(mid+1,r);}// 划分数组 ≤x 放左边,>x 放右边publicstaticintpartition(intl,intr,intx){intlow=l,xi=0;for(inti=l;i<=r;i++){if(arr[i]<=x){swap(low,i);if(arr[low]==x)xi=low;low++;}}swap(xi,low-1);// 确保 ≤x 区域的最后一个数字是 xreturnlow-1;}

这里再贴一个 c 风格的,双指针扫描写法的 partition,常见于数据结构教材中。

intpartition(intlow,inthigh,intx){while(low<high){while(low<high&&arr[high]>=x)high--;swap(low,high);while(low<high&&arr[low]<=x)low++;swap(low,high);}returnlow;}

时间复杂度

快速排序的最优时间复杂度和平均时间复杂度为O ( n log ⁡ n ) O(n\log n)O(nlogn),最坏时间复杂度为O ( n 2 ) O(n^2)O(n2)

对于最优情况,每一次选择的分界值都是序列的中位数,此时算法时间复杂度满足的递推式为T ( n ) = 2 T ( n 2 ) + Θ ( n ) T(n) = 2T\left( \frac{n}{2} \right)+Θ(n)T(n)=2T(2n)+Θ(n),由主定理,T ( n ) = Θ ( n log ⁡ n ) T(n)=Θ(n\log n)T(n)=Θ(nlogn)

对于最坏情况,每一次选择的分界值都是序列的最值,此时算法时间复杂度满足的递推式为T ( n ) = T ( n − 1 ) + Θ ( n ) T(n)=T(n-1)+Θ(n)T(n)=T(n1)+Θ(n),易得T ( n ) = Θ ( n 2 ) T(n)=Θ(n^2)T(n)=Θ(n2)

对于平均情况,每一次选择的分界值可以看作是等概率随机的,设划分值在k kk位置,那么:T ( n ) = 1 n ∑ k = 1 n ( T ( k − 1 ) + T ( n − k ) ) + n = 2 n ∑ k = 1 n T ( k ) + n T(n)=\frac{1}{n}\sum_{k=1}^n(T(k-1)+T(n-k))+n=\frac{2}{n}\sum_{k=1}^nT(k)+nT(n)=n1k=1n(T(k1)+T(nk))+n=n2k=1nT(k)+n
由数学归纳法可证明,其数量级为O ( n log ⁡ n ) O(n\log n)O(nlogn)

在实际中,几乎不可能达到最坏情况,而快速排序的内存访问遵循局部性原理(递归处理相邻子数组、原地分区,连续访问缓存行),所以多数情况下快速排序的表现大幅优于堆排序等其他复杂度为O ( n log ⁡ n ) O(n\log n)O(nlogn)的排序算法。

就空间复杂度而言,主要是递归造成的栈空间的使用,最好情况,递归树的深度为log ⁡ 2 n \log_{2}nlog2n,其空间复杂度也就为O ( log ⁡ n ) O(\log n)O(logn),最坏情况,需要进行n − 1 n-1n1递归调用,其空间复杂度为O ( n ) O(n)O(n),平均情况,空间复杂度也为O ( log ⁡ n ) O(\log n)O(logn)

优化

  • 当序列较短时,直接使用插入排序的效率更高;
  • 尾递归(迭代)可以缩减堆栈深度,提高整体性能。

我们还有两条主路径去优化:划分值m mm的选取划分的过程

划分值m mm

如果你用朴素方法中的固定方式选取划分值m mm,比如总选数列第一个或最后一个元素,毒瘤数据能够把朴素的快速排序卡成O ( n 2 ) O(n^2)O(n2),比如升序或降序数列。

  • 通过三数取中,即选取第一个、最后一个以及中间的元素中的中位数。

    • 对于非常大的待排序的数列,也有九数取中,不再详述。
  • 通过随机选取,即随机选一个[ l , r ] [l,r][l,r]上的下标对应的值。虽然随机的常数时间比较大,但只有这一下随机,才能在概率上把快速排序的时间复杂度收敛到O ( n log ⁡ n ) O(n\log n)O(nlogn)

划分过程

朴素方法中,每次划分的过程都只能确定一个位置的值,即p a r t i t i o n partitionpartition返回的位置。

三路快速排序

是快速排序和基数排序的混合。思想基于 荷兰国旗问题。

每次划分过程,将待排数列划分为三个部分:小于m mm、等于m mm以及大于m mm。在处理含有多个重复值的数组时,效率远高于朴素快速排序。其最佳时间复杂度为O ( n ) O(n)O(n)

在实现时,只需维护小于m mm的边界,和大于m mm的边界,等于的部分自然就唯一确定了。

荷兰国旗优化版随机快速排序

publicstaticintfirst,last;publicstaticvoidquickSort(intl,intr){if(l>=r)return;// [l, r+1) -> l + [0, r-l+1)intx=arr[l+(int)(Math.random()*(r-l+1))];partition(l,r,x);intleft=first;intright=last;// 记录一下 lastquickSort(l,left-1);// 经过左半边快排,last 可能被更改quickSort(right+1,r);}publicstaticvoidpartition(intl,intr,intx){first=l;last=r;inti=l;while(l<=last){if(arr[i]==x)i++;elseif(arr[i]<x)swap(first++,i++);elseswap(last--,i);}}
内省排序

是快速排序和堆排序的结合。保证了最差时间复杂度为O ( n log ⁡ n ) O(n\log n)O(nlogn)

内省排序将快速排序的最大递归深度限制为⌊ log ⁡ n ⌋ \lfloor \log n \rfloorlogn,超过限制时就转换为堆排序。这样既保留了快速排序内存访问的局部性,又可以防止快速排序在某些情况下性能退化为O ( n 2 ) O(n^2)O(n2)

SGI C++ STL 的stl_algo.hsort()函数的实现采用了内省排序算法。

习题

洛谷 P1177【模板】快速排序 模板
力扣 912. 排序数组 模板

#atom

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 21:32:21

【项目中的经验总结】

知识点一&#xff1a;多电源域下 Dummy Gate 的连接问题 1. 背景说明 在 MOS 管版图中&#xff0c;dummy gate 常用于满足工艺对栅极密度、对称性和边缘效应的要求。 Dummy gate 虽不参与功能&#xff0c;但仍具有栅氧层及栅-源 / 栅-漏寄生电容&#xff0c;不能视为“完全无效…

作者头像 李华
网站建设 2026/9/2 22:12:16

线性规划:库存切割问题

原文&#xff1a;towardsdatascience.com/linear-programming-the-stock-cutting-problem-dc6ba3bf3de1 本文深入探讨了线性规划如何解决一个称为“库存切割”的具体问题。在深入探讨本系列中的线性规划细节之前&#xff0c;我想提供一个具体的例子。本文将使用未在本文中定义的…

作者头像 李华
网站建设 2026/9/5 12:18:17

国抽对接规则

1、国抽可以同步下来 待填报之前的样品信息&#xff08;包括正在接样状态&#xff09;。 2、国抽可以同步下来 待填报且已维护基础表的项目信息&#xff08;基础表维护按照报送分类B及细类维护&#xff0c;或特殊样品的项目维护&#xff09;。

作者头像 李华
网站建设 2026/9/2 22:11:49

[特殊字符]️_开发效率与运行性能的平衡艺术[20260107171443]

作为一名经历过无数项目开发的工程师&#xff0c;我深知开发效率与运行性能之间的平衡是多么重要。在快节奏的互联网行业&#xff0c;我们既需要快速交付功能&#xff0c;又需要保证系统性能。今天我要分享的是如何在开发效率和运行性能之间找到最佳平衡点的实战经验。 &#…

作者头像 李华
网站建设 2026/9/2 22:12:19

c++ 17的std::variant对标c#的哪个类型

C# 的 System.Object、dynamic 和 C# 7.0 引入的 System.ValueTuple 以及 System.OneOf 等都可以实现类似功能&#xff0c;但最接近 C std::variant 语义的是&#xff1a;1. 官方方案&#xff1a;System.Object 和模式匹配&#xff08;最接近&#xff09;基础用法csharp// 类似…

作者头像 李华
网站建设 2026/9/3 4:58:50

狂揽77k Star!我用3分钟,给本地服务安上了公网域名

我经常需要在外面访问家里的 NAS&#xff0c;或者给客户演示我电脑上一个还没上线的项目。每次都被内网穿透搞得头疼不已&#xff0c;不是要研究复杂的路由器设置&#xff0c;就是要忍受那些又慢又不稳定的免费服务。传统内网穿透&#xff0c;太折腾了想靠自己搞定内网穿透&…

作者头像 李华