快速排序和归并排序的时间复杂度分析——通俗易懂
1|0一、前言
今天面试的时候,被问到归并排序的时间复杂度,这个大家都知道是O(nlogn)
,但是面试官又继续问,怎么推导出来的。这我就有点懵了,因为之前确实没有去真正理解这个时间复杂度是如何得出的,于是就随便答了一波(理解了之后,发现面试的时候答错了......)。
归并排序和快速排序,是算法中,非常重要的两个知识点,同时也是在面试中被问的非常频繁的内容,我明知如此,却没有彻底理解,真是太不应该了。所以,今天这篇博客就来分析一下这两种排序算法的时间复杂度是如何得出的。我查了许多篇博客,很多都是通过公式进行分析,十分难理解,下面我就结合自己的理解,使用通俗易懂的方式进行描述(为了好理解,可能会有些啰嗦)。
2|0二、正文
2|12.1 归并排序的时间复杂度分析
了解归并排序的应该都知道,归并排序的时间复杂度是O(nlogn)
,且这个时间复杂度是稳定的,不随需要排序的序列不同而产生波动。那这个时间复杂度是如何得来的呢?我们可以这样分析,假设我们需要对一个包含n
个数的序列使用归并排序,并且使用的是递归的实现方式,那么过程如下:
- 递归的第一层,将
n
个数划分为2
个子区间,每个子区间的数字个数为n/2
; - 递归的第二层,将
n
个数划分为4
个子区间,每个子区间的数字个数为n/4
; - 递归的第三层,将
n
个数划分为8
个子区间,每个子区间的数字个数为n/8
;
......
- 递归的第
logn
层,将n
个数划分为n
个子区间,每个子区间的数字个数为1
;
我们知道,归并排序的过程中,需要对当前区间进行对半划分,直到区间的长度为1
。也就是说,每一层的子区间,长度都是上一层的1/2
。这也就意味着,当划分到第logn层的时候,子区间的长度就是1了。而归并排序的merge
操作,则是从最底层开始(子区间为1
的层),对相邻的两个子区间进行合并,过程如下:
- 在第
logn
层(最底层),每个子区间的长度为1
,共n
个子区间,每相邻两个子区间进行合并,总共合并n/2
次。n
个数字都会被遍历一次,所有这一层的总时间复杂度为O(n)
;
......
- 在第二层,每个子区间长度为
n/4
,总共有4
个子区间,每相邻两个子区间进行合并,总共合并2
次。n
个数字都会被遍历一次,所以这一层的总时间复杂度为O(n)
; - 在第一层,每个子区间长度为
n/2
,总共有2
个子区间,只需要合并一次。n
个数字都会被遍历一次,所以这一层的总时间复杂度为O(n)
;
通过上面的过程我们可以发现,对于每一层来说,在合并所有子区间的过程中,n
个元素都会被操作一次,所以每一层的时间复杂度都是O(n)
。而之前我们说过,归并排序划分子区间,将子区间划分为只剩1
个元素,需要划分logn
次。每一层的时间复杂度为O(n),共有logn层,所以归并排序的时间复杂度就是O(nlogn)。
上面的描述算是非常详细了,应该不会太难理解。如果上面的过程还是不太理解,那么我们通过另外一种更直观的方式进行分析。上面描述的是递归的过程,下面我们通过非递归(迭代)方式实现的归并排序,再来分析一波,这种方式更加直观(为什么不直接通过非递归的方式描述,而是先通过递归的方式分析,是因为上面的过程也可以用来分析快速排序)。下面是通过非递归方式实现的归并排序代码,其中有两处分析时间复杂度的关键点,我标注出来了(重点关注注释):
上面的代码,merge
方法中的循环需要循环logn
次,每次循环都调用一次mergeSort
方法,mergeSort
方法的时间复杂度为O(n)
,所以很容易得出归并排序的时间复杂度为O(nlogn)
。
2|22.2 快速排序的时间复杂度
了解快速排序的应该知道,快速排序的时间复杂度在O(nlogn)~ O(n^2)
之间,下面我就来分别分析这两种情况:
(一)快速排序的最好情况O(nlogn)
这种情况下,其实和上面通过递归分析的归并排序很类似,理解了归并排序的时间复杂度分析,那这里应该也很好理解。快速排序的实现方式,就是在当前区间中选择一个轴,区间中所有比轴小的数都需要放到轴的左边,而比轴大的数则放到轴的右边。在理想的情况下,我们选取的轴刚好就是这个区间的中位数。也就是说,在操作之后,正好将区间分成了数字个数相等的左右两个子区间。此时就和归并排序基本一致了:
- 递归的第一层,
n
个数被划分为2
个子区间,每个子区间的数字个数为n/2
; - 递归的第二层,
n
个数被划分为4
个子区间,每个子区间的数字个数为n/4
; - 递归的第三层,
n
个数被划分为8
个子区间,每个子区间的数字个数为n/8
;
......
- 递归的第
logn
层,n
个数被划分为n
个子区间,每个子区间的数字个数为1
;
以上过程与归并排序基本一致,而区别就是,归并排序是从最后一层开始进行merge
操作,自底向上;而快速排序则是从第一层开始,交换区间中数字的位置,也就是自顶向下。但是,merge
操作和快速排序的调换位置操作,时间复杂度是一样的,对于每一个区间,处理的时候,都需要遍历一次区间中的每一个元素。这也就意味着,快速排序和归并排序一样,每一层的总时间复杂度都是O(n)
,因为需要对每一个元素遍历一次。而且在最好的情况下,同样也是有logn
层,所以快速排序最好的时间复杂度为O(nlogn)
。
(二)快速排序的最坏情况O(n^2)
下面我们再来说一说快速排序的最坏情况,这种情况就比较好理解了。什么是快速排序的最坏情况,那就是,对于每一个区间,我们在处理的时候,选取的轴刚好就是这个区间的最大值或者最小值。比如我们需要对n
个数排序,而每一次进行处理的时候,选取的轴刚好都是区间的最小值。于是第一次操作,在经过调换元素顺序的操作后,最小值被放在了第一个位置,剩余n-1
个数占据了2到n
个位置;第二次操作,处理剩下的n-1
个元素,又将这个子区间的最小值放在了当前区间的第1
个位置,以此类推......每次操作,都只能将最小值放到第一个位置,而剩下的元素,则没有任何变化。所以对于n
个数来说,需要操作n
次,才能为n
个数排好序。而每一次操作都需要遍历一次剩下的所有元素,这个操作的时间复杂度是O(n)
,所以总时间复杂度为O(n^2)
。
其实上面的过程,我们可以换一个角度理解:每次操作,找出最小值放到剩余区间的第一个位置,这不就是选择排序的实现方式吗?而选择排序的时间复杂度就是O(n^2)
,所以上面的过程也就O(n^2)
。
3|0三、总结
以上内容,就是我基于自己的理解,对快速排序和归并排序时间复杂度的分析。为了更好理解,我的描述都尽可能的详细,所以可能会有点啰嗦,但是我认为还是很通俗易懂的。希望这篇博客能够为之前对这两种排序算法理解不是特别清晰的人提供帮助,同时,若上面的内容存在错误或不足,欢迎指正或补充。
__EOF__

本文链接:https://www.cnblogs.com/tuyang1129/p/12857821.html
关于博主:在互联网洋流中垂死挣扎,但依旧乐观的Java小菜鸟一枚!
版权声明:转载博客请注明出处,并附上原文链接!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)