用通俗易懂的语言解释大O符号

用通俗易懂的语言解释大O符号

技术背景

在计算机科学里,我们常常要对算法的效率进行评估。不过,直接衡量软件程序的速度并非易事,得出的结果往往复杂,还存在诸多例外和特殊情况。当我们想要对比两个程序的“快慢”时,这些复杂因素会分散我们的注意力,起不到帮助作用。所以,人们尝试用尽可能简单的数学表达式来描述软件程序的速度。大O符号(Big O notation)就是这样一种工具,它用于表示算法复杂度的上界,能帮助我们分析算法在输入规模增大时的性能表现,进而比较不同算法的优劣。

实现步骤

理解大O符号的基本概念

大O符号是算法复杂度的一种相对表示,它描述了算法复杂度随输入规模增长的变化趋势。其核心要点如下:

  • 相对性:只能对同类算法进行比较,例如比较两个执行算术运算的算法才有意义。
  • 表示性:大O符号(最简单形式)将算法比较简化为一个变量,该变量基于观察或假设选取。
  • 复杂性:反映算法的操作次数与输入规模之间的关系,例如已知排序10,000个元素所需的时间,可据此推测排序一百万个元素所需的时间。

常见的大O复杂度类型及示例

1. O(1) - 常数复杂度

无论输入规模如何变化,算法的执行时间始终保持不变。例如,通过索引访问数组元素:

1
2
arr = [1, 2, 3, 4, 5]
print(arr[2]) # 访问数组中索引为2的元素,时间复杂度为O(1)

2. O(log n) - 对数复杂度

算法的执行时间随输入规模的对数增长而增长。典型的例子是在电话簿中查找姓名的二分查找算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
target = 7
result = binary_search(arr, target)
print(result) # 二分查找,时间复杂度为O(log n)

3. O(n) - 线性复杂度

算法的执行时间与输入规模成正比。比如,遍历链表以统计元素个数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Node:
def __init__(self, value):
self.value = value
self.next = None

# 创建链表
head = Node(1)
node2 = Node(2)
node3 = Node(3)
head.next = node2
node2.next = node3

# 遍历链表并计数
count = 0
current = head
while current:
count += 1
current = current.next
print(count) # 遍历链表,时间复杂度为O(n)

4. O(n log n) - 线性对数复杂度

常见于一些高效的排序算法,如堆排序和快速排序:

1
2
3
4
5
6
7
8
9
10
11
12
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr[len(arr) // 2]
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return quick_sort(left) + middle + quick_sort(right)

arr = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
sorted_arr = quick_sort(arr)
print(sorted_arr) # 快速排序,平均时间复杂度为O(n log n)

5. O(n²) - 二次复杂度

算法的执行时间与输入规模的平方成正比。以冒泡排序为例:

1
2
3
4
5
6
7
8
9
10
11
def bubble_sort(arr):
n = len(arr)
for i in range(n):
for j in range(0, n - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
return arr

arr = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
sorted_arr = bubble_sort(arr)
print(sorted_arr) # 冒泡排序,时间复杂度为O(n²)

6. O(n!) - 阶乘复杂度

算法的执行时间随输入规模的阶乘增长而增长,如旅行商问题的暴力解法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import itertools

def traveling_salesman(graph):
num_cities = len(graph)
all_permutations = itertools.permutations(range(num_cities))
min_distance = float('inf')
for perm in all_permutations:
distance = 0
for i in range(num_cities - 1):
distance += graph[perm[i]][perm[i + 1]]
distance += graph[perm[-1]][perm[0]]
min_distance = min(min_distance, distance)
return min_distance

graph = [
[0, 10, 15, 20],
[10, 0, 35, 25],
[15, 35, 0, 30],
[20, 25, 30, 0]
]
result = traveling_salesman(graph)
print(result) # 旅行商问题暴力解法,时间复杂度为O(n!)

分析算法复杂度的步骤

  1. 确定输入规模:明确算法处理的数据量大小,通常用n表示。
  2. 找出关键操作:确定算法中执行次数最多的操作,这些操作决定了算法的复杂度。
  3. 计算操作次数:分析关键操作的执行次数与输入规模n的关系。
  4. 忽略常数和低阶项:大O符号关注的是复杂度的增长趋势,因此忽略常数因子和低阶项。

核心代码

以下是几个常见算法复杂度分析的Python代码示例:

线性复杂度示例

1
2
3
4
5
6
7
8
9
def sum_list(lst):
total = 0
for num in lst:
total += num
return total

lst = [1, 2, 3, 4, 5]
result = sum_list(lst)
print(result) # 时间复杂度为O(n)

二次复杂度示例

1
2
3
4
5
6
7
def print_pairs(lst):
for i in lst:
for j in lst:
print(i, j)

lst = [1, 2, 3]
print_pairs(lst) # 时间复杂度为O(n²)

最佳实践

  • 优先选择复杂度低的算法:在设计算法时,尽量选择复杂度较低的算法,如能用O(log n)的算法就不用O(n²)的算法。
  • 利用数据结构优化算法:合理使用数据结构可以显著提高算法的效率。例如,使用哈希表可以将查找操作的时间复杂度从O(n)降低到O(1)。
1
2
3
4
5
# 使用哈希表进行查找
hash_table = {1: 'apple', 2: 'banana', 3: 'cherry'}
key = 2
if key in hash_table:
print(hash_table[key]) # 查找操作,时间复杂度为O(1)
  • 提前预估算法复杂度:在编写代码之前,先分析算法的复杂度,评估其在不同输入规模下的性能表现。

常见问题

大O符号与实际运行时间的关系

大O符号描述的是算法复杂度的上界,它关注的是算法复杂度随输入规模增长的趋势,而不是实际的运行时间。实际运行时间还受到硬件性能、编程语言、代码实现等因素的影响。

平均情况和最坏情况复杂度的区别

  • 平均情况复杂度:基于输入的概率分布计算算法的平均执行时间。例如,快速排序的平均情况复杂度为O(n log n),但在某些特殊输入下,最坏情况复杂度为O(n²)。
  • 最坏情况复杂度:考虑算法在所有可能输入下的最长执行时间,大O符号通常表示的是最坏情况复杂度。

如何处理多维大O复杂度

在一些算法中,可能存在多个输入变量影响算法的复杂度,例如字符串搜索算法的复杂度可能是O(N + M),其中N是文本的长度,M是查询的长度。在分析这类算法时,需要考虑所有相关的输入变量。


用通俗易懂的语言解释大O符号
https://119291.xyz/posts/2025-05-08.plain-english-explanation-of-big-o-notation/
作者
ww
发布于
2025年5月8日
许可协议