时间复杂度

187 阅读3分钟

时间复杂度是衡量算法运行时间随输入规模增长而变化的一个指标。它不是具体的运行时间,而是一个增长趋势的表示。


1. 什么是时间复杂度?

  • 定义:时间复杂度表示算法执行时间随输入规模(通常用 nn 表示)增长的趋势。
  • 表示方法:用大 OO 符号(Big-O Notation)表示,例如 O(n)O(n)、O(n2)O(n2) 等。
  • 意义:帮助我们分析算法的效率,尤其是在输入规模很大时。

2. 如何求时间复杂度?

步骤 1:找出算法的基本操作

  • 基本操作是算法中执行次数最多的操作,通常是循环、递归或条件判断中的操作。

步骤 2:计算基本操作的执行次数

  • 根据输入规模 nn,计算基本操作执行的次数。

步骤 3:用大 OO 表示法表示

  • 忽略常数项和低阶项,只保留最高阶的项。

3. 常见的时间复杂度

以下是常见的时间复杂度,按效率从高到低排序:

时间复杂度名称例子
O(1)O(1)常数时间访问数组中的元素
O(log⁡n)O(logn)对数时间二分查找
O(n)O(n)线性时间遍历数组或链表
O(nlog⁡n)O(nlogn)线性对数时间快速排序、归并排序
O(n2)O(n2)平方时间冒泡排序、选择排序
O(2n)O(2n)指数时间递归求解斐波那契数列
O(n!)O(n!)阶乘时间旅行商问题(穷举所有可能的路径)

4. 如何计算时间复杂度?

例子 1:常数时间 O(1)

int a = 10; // 1次
int b = 20; // 1次
int c = a + b; // 1次
  • 基本操作执行次数是固定的,与输入规模 n 无关。
  • 时间复杂度为 O(1)。

例子 2:线性时间 O(n)

for (int i = 0; i < n; i++) { // 循环 n 次
    System.out.println(i); // 1次
}
  • 基本操作 System.out.println(i) 执行了 n 次。
  • 时间复杂度为 O(n)。

例子 3:平方时间 O(n2)

for (int i = 0; i < n; i++) { // 循环 n 次
    for (int j = 0; j < n; j++) { // 循环 n 次
        System.out.println(i + "," + j); // 1次
    }
}
  • 基本操作 System.out.println(i + "," + j) 执行了n×n=n2次。
  • 时间复杂度为O(n2)。

例子 4:对数时间 O(log⁡n)

int i = 1;
while (i < n) {
    i = i * 2; // 每次 i 乘以 2
}
  • 每次循环i乘以 2,直到i大于或等于n。
  • 循环次数是 log⁡2n(因为2k=n,所以 k=log⁡2n)

例子 5:线性对数时间 O(nlog⁡n)

for (int i = 0; i < n; i++) { // 循环 n 次
    int j = 1;
    while (j < n) { // 循环 log n 次
        j = j * 2;
    }
}
  • 外层循环执行 n 次,内层循环执行 log⁡n 次。
  • 总操作次数为 n×log⁡n。
  • 时间复杂度为 O(nlog⁡n)。

5. 时间复杂度的计算规则

  1. 忽略常数项

    • O(2n) 简化为 O(n)。
    • O(100) 简化为 O(1)。
  2. 忽略低阶项

    • O(n2+n) 简化为 O(n2)。
    • O(n+log⁡n) 简化为 O(n)。
  3. 嵌套循环

    • 外层循环执行 n 次,内层循环执行 m 次,时间复杂度为 O(n×m)。
  4. 递归算法

    • 递归的时间复杂度通常需要分析递归的深度和每次递归的操作次数。

6. 递归算法的时间复杂度

递归的时间复杂度通常用递归树主定理来计算。

例子:斐波那契数列

int fib(int n) {
    if (n <= 1) return n;
    return fib(n - 1) + fib(n - 2);
}
  • 每次递归调用会分解为两个子问题,时间复杂度为 O(2n)。

7. 总结

  • 时间复杂度是描述算法运行时间与输入规模关系的指标。

  • 计算方法

    1. 找出基本操作。
    2. 计算基本操作的执行次数。
    3. 用大 OO 表示法表示。
  • 常见时间复杂度:O(1)、O(logn)、O(n)、O(nlogn)、O(n2)、O(2n)。