时间与空间复杂度
时间复杂度
以下是常见的时间复杂度,按效率从高到低排序:
| 时间复杂度 | 名称 | 示例 |
|---|---|---|
| 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²) - 平方空间
特点:额外空间与 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
关键点:
- 每次运算需要占用的额外空间是原来的一半

浙公网安备 33010602011771号