理解时间复杂度和空间复杂度

715 阅读5分钟

1. 时间复杂度(Time Complexity)

定义

  • 时间复杂度是用来衡量一个算法执行时间随着输入规模增长而增长的趋势。它描述了算法运行时间与输入规模之间的关系,重点关注的是当输入规模趋向于无穷大时,算法执行时间的增长量级。简单来说,就是当数据量越来越大时,算法运行时间的变化快慢。

计算方法

  • 步骤一:确定基本操作
    • 首先要确定算法中的基本操作,即执行次数最多、最能体现算法运行时间特性的操作。例如,在一个数组遍历算法中,访问数组元素的操作(如array[i])就是基本操作。
  • 步骤二:分析基本操作执行次数与输入规模的关系
    • 用一个函数来表示基本操作执行次数与输入规模(通常用n)表示)之间的关系。例如,对于一个简单的线性遍历长度为n的数组的算法,基本操作(访问数组元素)会执行n次,所以执行次数函数为T(n)
  • 步骤三:忽略常数项和低阶项,只保留最高阶项并确定复杂度表示
    • 最后,按照大O记号(Big O notation)来表示时间复杂度。大O记号给出了算法运行时间的一个上界。例如,对于T(n)=3n + 5,当n趋向于无穷大时,常数项5和系数3对增长趋势的影响相对较小,所以时间复杂度表示为O(n)。

常见时间复杂度示例

  • 常数时间复杂度 O(1)

    • 例如,访问数组中的某个特定元素(已知索引),无论数组大小如何,操作次数都是固定的。
    array = [1, 2, 3, 4, 5] 
    print(array[2]) 
    
    • 这里的时间复杂度就是O(1),因为无论数组长度n是多少,执行打印操作只需要一次访问,基本操作执行次数与n无关。
  • 线性时间复杂度 O(n)

    • 如遍历一个长度为n的数组,基本操作(访问数组元素)的执行次数与数组长度n成正比。
    array = [1, 2, 3,..., n] 
    for element in array: 
        print(element) 
    
    • 这里的时间复杂度是O(n),因为循环会执行n次,基本操作(打印元素)的执行次数是n次。
  • 二次时间复杂度 O(n^2)

    • 以嵌套循环遍历二维数组为例,假设有一个n x n的二维数组。
    matrix = [[1, 2, 3,..., n], [1, 2, 3,..., n],..., [1, 2, 3,..., n]] 
    for row in matrix: 
        for element in row: 
            print(element) 
    
    • 外层循环执行n次,对于每次外层循环,内层循环也执行n次,所以总的基本操作(打印元素)执行次数约为n x n = n^2,时间复杂度是O(n^2)。

2. 空间复杂度(Space Complexity)

定义

  • 空间复杂度是衡量一个算法在运行过程中临时占用存储空间大小随输入规模增长而增长的趋势。它主要关注算法执行过程中所需要的额外存储空间,不包括输入数据本身所占的空间。

计算方法

  • 步骤一:确定算法运行过程中的辅助空间使用情况
    • 分析算法运行时除了输入数据本身之外,还需要使用哪些额外的存储空间,如临时变量、递归调用栈等。例如,在一个简单的求和算法中,可能只需要一个变量来存储累加结果。
  • 步骤二:分析辅助空间大小与输入规模的关系
    • 用一个函数来表示辅助空间大小与输入规模(通常用n表示)之间的关系。例如,对于一个算法,它只需要一个额外的变量,无论输入规模n如何变化,这个变量占用的空间都是固定的,那么辅助空间大小函数可以表示为S(n)=1
  • 步骤三:按照大O记号表示空间复杂度
    • 同样,忽略常数项和低阶项,只保留最高阶项并确定空间复杂度表示。例如,对于S(n)=2n + 3,空间复杂度表示为O(n)

常见空间复杂度示例

  • 常数空间复杂度 O(1)
    • 例如,交换两个变量的值,只需要几个临时变量来存储中间结果,这些变量的数量不随输入规模变化。
    a = 10 
    b = 20 
    temp = a 
    a = b 
    b = temp 
    
    • 这里的空间复杂度是O(1),因为无论要交换的值是多少,只需要一个额外的变量temp,辅助空间大小与输入规模无关。
  • 线性空间复杂度 O(n)
    • 例如,创建一个长度为n的数组来存储数据。
    python n = 10 
    array = [0 for _ in range(n)] 
    
    • 这里的空间复杂度是O(n),因为创建的数组长度与输入规模n成正比,辅助存储空间大小随n线性增长。
  • 递归栈空间复杂度(与递归深度有关)
    • 以计算斐波那契数列的递归算法为例。
    def fibonacci(n): 
        if n <= 1: 
            return n 
        return fibonacci(n - 1) + fibonacci(n - 2) 
    
    • 当计算fibonacci(n) 时,递归调用栈的深度最多可达 n(在最坏情况下),所以空间复杂度是O(n)。因为递归调用栈需要为每个递归调用保存函数的局部变量、返回地址等信息,占用的空间与递归深度成正比。