快速排序与归并排序时间复杂度:递归与非递归双重解析

慢慢来
慢慢来
发布时间:2026-10-15| 4人浏览过
原创

面试中常考归并排序和快速排序的时间复杂度,知其然更需知其所以然。本文通过非递归代码视角和递归树逻辑,深入剖析这两种核心排序算法的复杂度来源,解释为何归并排序稳定在 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)。

以上就是快速排序与归并排序时间复杂度的详细内容,更多关于算法分析的资料请关注本站其它相关文章!

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。