快速排序与归并排序时间复杂度:递归与非递归双重解析
面试中常考归并排序和快速排序的时间复杂度,知其然更需知其所以然。本文通过非递归代码视角和递归树逻辑,深入剖析这两种核心排序算法的复杂度来源,解释为何归并排序稳定在 O(nlogn),而快速排序则在 O(nlogn) 到 O(n^2) 之间波动。
归并排序的时间复杂度推导
归并排序的时间复杂度为 O(nlogn)。这一结论可以通过递归过程和迭代过程两个维度来验证。
第1步:基于递归树的层数分析
假设对包含 n 个数的序列进行归并排序,递归划分过程如下:
- 第一层将
n个数划分为2个子区间,每个区间大小n/2; - 第二层继续划分子区间,得到
4个区间,大小为n/4; - 以此类推,直到第
logn层,区间长度缩减为1。
在自底向上的合并过程中,每一层都需要遍历当前的所有元素以完成合并。
- 最底层:合并 n/2 次,遍历所有 n 个元素,复杂度 O(n)。
- 倒数第二层:合并次数减半但区间变大,总元素仍为 n,复杂度同样为 O(n)。
由于总共存在 logn 层,且每层耗时 O(n),因此总时间复杂度为 O(n × logn)。
第2步:基于非递归代码的结构验证
通过迭代方式实现归并排序可以更直观地看到层级的变化。以下是关键的伪代码结构:
/**
* 此方法用来定义子区间大小,子区间大小从1->2->4->8 ... ->n/2
* 可以近似地认为进行了logn次
*/
public static void merge(int[] arr) {
// 关键点1:划分子区间,每一次的子区间长度是上一次的两倍,所以这个循环需要执行logn次
for(int i = 1;i<arr.length;i *= 2){
// 关键点2:此方法每次执行的时间复杂度为O(n),具体看下方
mergeSort(arr,i);
}
}
/**
* 以下方法,每次执行的时间复杂度都是O(n),
* 因为需要将arr数组的每gap个数子,作为一个子区间,
* 然后对相邻的两个子区间执行归并排序的merge操作,
* 所以在这个方法中,arr数组中的每一个数都会在merge操作中,
* 被处理一次,所以下面这个方法的时间复杂度为O(n)
*/
public static void mergeSort(int[] arr, int gap) {
int[] tmp = new int[arr.length];
int index = 0;
int start1 = 0;
int end1 = start1 + gap - 1;
int start2 = end1 + 1;
int end2 = (start2 + gap - 1)>=arr.length?arr.length-1:start2+gap-1;
while(start2<arr.length){
while(start1<=end1&&start2<=end2){
if(arr[start1]<arr[start2]){
tmp[index++] = arr[start1++];
}else{
tmp[index++] = arr[start2++];
}
}
while(start1<=end1){
tmp[index++] = arr[start1++];
}
while(start2<=end2){
tmp[index++] = arr[start2++];
}
start1 = end2+1;
end1 = start1 + gap - 1;
start2 = end1 + 1;
end2 = (start2 + gap - 1)>=arr.length?arr.length-1:start2+gap-1;
}
while(start1<arr.length){
tmp[index++] = arr[start1++];
}
for(int j = 0;j<tmp.length;j++){
arr[j] = tmp[j];
}
}上述代码中,外层 for 循环控制子区间大小的倍增(i *= 2),共执行 logn 次。内层的 mergeSort 方法负责当前规模下的线性扫描合并,时间复杂度为 O(n)。二者相乘,结果依然为 O(nlogn)。
快速排序的时间复杂度分析
快速排序的核心在于选择轴点(Pivot)。其复杂度取决于轴点的选取是否均衡。
第1步:最好情况分析 O(nlogn)
当每次选择的轴点都恰好是当前区间的中间值(中位数)时,分区极其均衡:
- 第一层:分为 2 个大小区间(约 n/2);
- 第三层:分为 8 个小区间(约 n/8);
- 直至 logn 层区间长度变为 1。
在此过程中,每一层的所有分区加起来的总元素量均为 n,即每层处理耗时 O(n)。既然有 logn 层,总耗时即为 O(nlogn)。
第2步:最坏情况分析 O(n^2)
最坏情况发生在每次选取的轴点都是当前区间的最大值或最小值时(例如:序列本身已经有序,且始终选第一个元素做轴点)。此时分区极度失衡:
- 第一次操作:找出最小值置于首位,剩余
n-1个元素构成新子区间; - 第二次操作:在
n-1个元素中再找最小值……
这等同于每次只能确定一个元素的最终位置。为了排好 n 个数,需要进行 n 次遍历操作。每次操作的平均长度约为 n/2,故总耗时趋近于 n^2/2,即 O(n^2)。
从另一个角度看,这种“依次选出极值”的操作模式,在逻辑上类似选择排序,而选择排序的固有复杂度正是 O(n^2)。
总结
归并排序利用稳定的分治策略,无论数据初始状态如何,均能保持 O(nlogn) 的效率;快速排序则高度依赖轴点的选择,虽然最优表现同为 O(nlogn),但在极端不平衡的数据下会退化至 O(n^2)。
以上就是快速排序与归并排序时间复杂度的详细内容,更多关于算法分析的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













