时间与空间复杂度

时间复杂度

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

时间复杂度 名称 示例
O(1) 常数时间 数组随机访问、哈希表查找
O(log n) 对数时间 二分查找、平衡二叉搜索树(AVL、红黑树)
O(n) 线性时间 遍历数组、链表查找
O(n log n) 线性对数时间 快速排序、归并排序、堆排序
O(n²) 平方时间 冒泡排序、选择排序、插入排序
O(n³) 立方时间 某些动态规划问题(如矩阵乘法)
O(2ⁿ) 指数时间 递归计算斐波那契数列、暴力破解算法
O(n!) 阶乘时间 旅行商问题(穷举所有排列组合)

1. O(1) - 常数时间

list = (0,1,2,3)

print(list[2]) # 因为是数组,直接取,一步到位

关键点:无循环,单次操作。


2. O(log n) - 对数时间

数学知识a^b = c,则 logₐc = b,表示 b是以a为底c的对数。比如 log₂8 = 3,因为 2^3=8

计算机中通常默认以 2 为底来描述时间复杂度,所以 O(log n) 完整的写法是 O(log₂n)(理解一下:因为以 2 为底,所以每次运算会过滤一半的数据)

O(log n) 表示算法的运算次数随着 n 的增长而对数增长

# 经典的二分查找:每比较一次过滤一半的数据
def binary_search(arr, target):
    left, right = 0, len(arr) - 1
    while left <= right:
        mid = (left + right) // 2    # 取中间点
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:      # 目标在右半部分
            left = mid + 1
        else:                        # 目标在左半部分
            right = mid - 1
    return -1

关键点:列表元素每增加一倍,运算次数只 +1


3. O(n) - 线性时间

for i in range(n):
    print(i)

关键点:有多少元素就运算多少次(专业的说法是成正比,因为一般会省略常数,就像对数一般以2为底)


4. O(n log n) - 线性对数时间

  • 把n个苹果反复对半切分(log n次)
  • 每次切分都需要处理所有当前块(n次操作)
  • 总工作量 = 切分次数 × 每次工作量 = O(n) * O(log n) = O(n log n)
def linearithmic_example(n):
    for i in range(n):          # O(n)
        j = 1
        while j < n:            # O(log n)
            print(i, j)
            j *= 2

# 示例调用
linearithmic_example(4)  # 输出: (0,1), (0,2), (1,1), (1,2), ...

关键点:外层循环 O(n) + 内层对数循环 O(log n)


5. O(n²) - 平方时间

for i in range(n):        # O(n)
    for j in range(n):    # O(n)
        print(i, j)				# O(n*n) => O(n^2)

关键点:双层嵌套循环,每层循环 n 次。

for i in range(n):        # O(n)
    for j in range(m):    # O(m)
        print(i, j)				# O(n*m) => O(n^m)

O(n^m) 当 m 足够小或者是一个指定的常数时也可以可以表示为 O(n^2)


6. O(n³) - 立方时间

for i in range(n):            # O(n)
    for j in range(n):        # O(n)
        for k in range(n):    # O(n)
            print(i, j, k)		# O(n*n*n) => O(n^3)

关键点:三层嵌套循环(慎用!)。


7. O(2ⁿ) - 指数时间

def exponential_example(n):
    for i in range(2**n):   # 循环次数: 2ⁿ
        print(i)

# 示例调用
exponential_example(3)  # 输出: 0, 1, 2, 3, 4, 5, 6, 7

关键点:运算次数是 n 的指数(慎用!)

当 n = 10,O(n^2) => 10*10 = 100 ;O(2^n) => 2*2*2*2*2*2*2*2*2*2 = 1024


8. O(n!) - 阶乘时间

from itertools import permutations

def factorial_example(n):
    items = list(range(n))
    for p in permutations(items):  # 排列数: n!
        print(p)

# 示例调用
factorial_example(3)  # 输出: (0,1,2), (0,2,1), (1,0,2), ..., (2,1,0)

关键点:穷举所有排列组合(慎用!仅适用于极小 n)。

空间复杂度

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

空间复杂度 名称 示例
O(1) 常数空间 原地排序(冒泡、选择、插入排序)
O(n) 线性空间 归并排序、动态规划数组
O(n²) 平方空间 二维动态规划表(如最长公共子序列)
O(log n) 对数空间 递归调用栈(如二分递归)

1. O(1) - 常数空间

特点:算法使用的额外空间不随输入规模 n 变化,仅适用原来的空间就能完成运算,比如冒泡、遍历、 二分查找等
示例(冒泡排序):

def bubble_sort(arr):
    n = len(arr)
    for i in range(n):
        for j in range(n-i-1):
            if arr[j] > arr[j+1]:
                arr[j], arr[j+1] = arr[j+1], arr[j]  # 原地交换
    return arr

关键点

  • 仅使用固定数量的临时变量(i, j
  • 不依赖输入规模的额外存储

2. O(n) - 线性空间

特点:额外空间与输入规模 n 成正比 ,需要额外 0.5 倍、1倍、2倍空间等都是 O(n)

示例1(动态数组扩容,每次扩展 0.5 倍)

class DynamicArray:
    def __init__(self):
        self._capacity = 1  # 初始容量
        self._size = 0      # 当前元素数量
        self._data = [None] * self._capacity

    def append(self, value):
        # 容量不足时,按 1.5 倍扩容
        if self._size == self._capacity:
            self._resize(int(self._capacity * 1.5))  # 扩容 0.5 倍(新容量 = 旧容量 × 1.5)
        self._data[self._size] = value
        self._size += 1

    def _resize(self, new_capacity):
        new_data = [None] * new_capacity
        for i in range(self._size):
            new_data[i] = self._data[i]  # 复制旧数据
        self._data = new_data
        self._capacity = new_capacity

    def __str__(self):
        return str(self._data[:self._size])

关键点

  • 需要存储与输入规模线性相关的数据(如递归栈、动态数组)

3. O(n²) - 平方空间

特点:额外空间与 成正比

def duplicate_elements(arr):
    result = []  # 额外空间大小 = 2 * len(arr) → O(N)
    for num in arr:
        result.append(num)
        result.append(num)
    return result

4. O(log n) - 对数空间

特点:每次运算会额外占用的原来的一半的空间

示例1(找到基数,一次循环,新数组占用空间是原来的一半):

def extract_odds(arr):
    odds = []  # 新数组存储奇数
    for num in arr:
        if num % 2 != 0:  # 判断是否为奇数
            odds.append(num)
    return odds

关键点

  • 每次运算需要占用的额外空间是原来的一半
posted @ 2025-04-26 17:32  CyrusHuang  阅读(57)  评论(0)    收藏  举报