一.原理分析
1. 时间复杂度
这是最重要的评估指标之一,它描述了算法的运行时间如何随着输入规模的增加而变化。
核心思想 :我们并不关心具体的运行时间(比如多少毫秒),而是关心增长的趋势。当输入规模n变得非常大时,常数因子和低阶项的影响会变得微不足道,起主导作用的是最高阶项。
大O表示法:用来描述算法运行时间的上界,即最坏情况下的性能。
常见复杂度(从优到劣):
O(1): 常数时间。无论输入多大,运行时间都不变。
python
def constant_time(arr):
return arr[0] if arr else None # 无论数组多长,只做一次操作
def access_dict(dictionary, key):
return dictionary.get(key) # 字典查找平均O(1)
# 示例:数组索引、字典查找、算术运算
O(log n): 对数时间。运行时间随输入规模呈对数增长,效率非常高(如二分查找)。
python
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
# 输入规模n,循环次数:log₂n
O(n): 线性时间。运行时间与输入规模成正比。
python
def linear_search(arr, target):
for i, item in enumerate(arr): # 遍历整个数组
if item == target:
return i
return -1
def sum_array(arr):
total = 0
for num in arr: # 遍历每个元素
total += num
return total
# 输入规模n,操作次数:n
O(n log n): 线性对数时间。许多高效排序算法的复杂度(如归并排序、快速排序的平均情况)。
python
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid]) # O(log n) 层递归
right = merge_sort(arr[mid:])
return merge(left, right) # 每层合并 O(n)
def merge(left, right):
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
# 典型算法:归并排序、快速排序(平均情况)、堆排序
O(n²): 平方时间。常见于简单的双重循环(如冒泡排序)。
python
def bubble_sort(arr):
n = len(arr)
for i in range(n): # n 次循环
for j in range(0, n-i-1): # 平均 n/2 次循环
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
return arr
def find_pairs(arr):
pairs = []
for i in range(len(arr)): # n 次循环
for j in range(i+1, len(arr)): # n-i-1 次循环
pairs.append((arr[i], arr[j]))
return pairs
# 总操作次数:n × (n-1)/2 ≈ n²/2 = O(n²)
O(2^n): 指数时间。通常出现在暴力求解问题中,当n较大时几乎不可用。
python
def fibonacci_naive(n):
if n <= 1:
return n
return fibonacci_naive(n-1) + fibonacci_naive(n-2) # 两次递归调用
# 递归树:每个节点分裂为两个子节点
# 时间复杂度:O(2^n)
O(n!): 阶乘时间。性能最差,通常用于解决旅行商等问题。
python
def generate_permutations(arr):
if len(arr) <= 1:
return [arr]
permutations = []
for i in range(len(arr)):
rest = arr[:i] + arr[i+1:]
for p in generate_permutations(rest): # 递归调用 (n-1)! 次
permutations.append([arr[i]] + p)
return permutations
# n个元素的排列数:n!,时间复杂度:O(n!)
2. 空间复杂度
空间复杂度 衡量的是算法在运行过程中临时占用的存储空间大小随输入规模增长的变化趋势。
简单来说,它回答的问题是:"当我的输入数据量翻倍时,这个算法需要多少额外的内存?"
O(1) - 常数空间
算法所需的额外空间不随输入规模n变化。
python
def constant_space(n):
total = 0 # O(1) - 1个变量
for i in range(n): # i 也是 O(1) - 每次循环复用同一个变量
total += i
return total
# 无论n多大,只用了固定数量的变量
O(n) - 线性空间
算法所需的额外空间与输入规模n成正比。
python
def linear_space(n):
result = [] # O(n)
for i in range(n):
result.append(i) # 列表长度随n线性增长
return result
def another_linear(n):
hash_map = {} # O(n)
for i in range(n):
hash_map[i] = i * 2 # 字典大小随n线性增长
return hash_map
O(n²) - 平方空间
算法所需的额外空间与输入规模n的平方成正比。
python
def quadratic_space(n):
matrix = [] # O(n²)
for i in range(n):
row = [] # 每行 O(n)
for j in range(n):
row.append(i * j) # 总共 n * n = n² 个元素
matrix.append(row)
return matrix
O(log n) - 对数空间
通常出现在递归算法中。
python
def recursive_function(n):
if n <= 1:
return n
# 递归深度为 O(log n),调用栈空间也是 O(log n)
return recursive_function(n // 2) + recursive_function(n // 2)
二.Python性能评估的实践工具
1. 时间测量工具
timeit 模块 - 首选工具
python
import timeit
# 方法1:使用timeit.timeit()
def test_function():
return sum(range(1000))
# 重复执行10000次,计算总时间
time_taken = timeit.timeit(test_function, number=10000)
print(f"平均时间: {time_taken/10000:.6f}秒")
# 方法2:使用timeit.repeat()
times = timeit.repeat(test_function, number=1000, repeat=5)
print(f"最佳时间: {min(times)/1000:.6f}秒")
print(f"最差时间: {max(times)/1000:.6f}秒")
print(f"平均时间: {sum(times)/len(times)/1000:.6f}秒")
2. 内存分析工具
memory_profiler
python
from memory_profiler import profile
@profile
def memory_intensive_function():
# 创建大量数据
big_list = [i for i in range(100000)]
big_dict = {i: str(i) for i in range(100000)}
return big_list, big_dict
if __name__ == "__main__":
memory_intensive_function()
三、完整的性能评估实践示例
让我们通过一个具体的例子来展示完整的评估流程。
python
import timeit
import random
from collections import defaultdict
import sys
def linear_search(arr, target):
"""线性查找 - O(n)"""
for i, item in enumerate(arr):
if item == target:
return i
return -1
def binary_search(arr, target):
"""二分查找 - O(log n)"""
low, high = 0, len(arr) - 1
while low <= high:
mid = (low + high) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
low = mid + 1
else:
high = mid - 1
return -1
def performance_comparison():
"""完整的性能比较函数"""
# 准备测试数据
sizes = [100, 1000, 10000, 100000]
results = defaultdict(dict)
for size in sizes:
# 生成有序数据(二分查找需要有序数据)
sorted_data = list(range(size))
target = random.choice(sorted_data) # 确保目标存在
# 测试线性查找
linear_time = timeit.timeit(
lambda: linear_search(sorted_data, target),
number=1000
)
# 测试二分查找
binary_time = timeit.timeit(
lambda: binary_search(sorted_data, target),
number=1000
)
results[size] = {
'linear_search': linear_time,
'binary_search': binary_time,
'ratio': linear_time / binary_time
}
# 输出结果
print("性能比较结果:")
print("数据规模\t线性查找\t二分查找\t速度比")
print("-" * 50)
for size in sizes:
result = results[size]
print(f"{size}\t\t{result['linear_search']:.6f}\t"
f"{result['binary_search']:.6f}\t"
f"{result['ratio']:.1f}x")
if __name__ == "__main__":
performance_comparison()