时间复杂度(Time Complexity)
在上一节中我们介绍了 什么是算法。
但有一个更重要的问题:
如果有多个算法可以解决同一个问题
哪一个更好?
例如:
算法A:计算 100 万次
算法B:计算 1 次
显然 算法B更快。
但在程序中,我们不可能真的每次都去计算执行时间,所以计算机科学家提出了一种 评估算法效率的方法:
时间复杂度
什么是时间复杂度
时间复杂度:描述算法运行时间随数据规模增长的变化趋势。
简单理解:
数据量变大时
算法会变慢多少
例如:
数据量 算法A 算法B
10 10次 1次 100 100次 1次 1000 1000次 1次
可以看到:
算法A的执行次数随着 n 增长
算法B几乎不变
所以:
算法B更优
大 O 表示法
时间复杂度通常用:
O(...)
表示。
读作:
Big O
例如:
O(1)
O(n)
O(n²)
O(log n)
它表示的是:
算法增长趋势
而不是 精确执行时间。
常见时间复杂度
O(1) 常数时间
无论数据量多大,执行次数基本不变。
例如数组取值:
let arr = [10,20,30,40]
print(arr[2])
访问数组元素时:
只执行一次
所以:
时间复杂度 = O(1)
O(n) 线性时间
执行次数和数据量 成正比。
例如遍历数组:
func printArray(_ arr: [Int]) {
for num in arr {
print(num)
}
}
如果数组长度是:
n
循环就会执行:
n 次
所以:
时间复杂度 = O(n)
O(n²) 平方时间
常见于 嵌套循环。
例如:
func printPairs(_ arr: [Int]) {
for i in 0..<arr.count {
for j in 0..<arr.count {
print(arr[i], arr[j])
}
}
}
如果数组长度是:
n
执行次数是:
n × n
所以:
时间复杂度 = O(n²)
O(log n) 对数时间
这种复杂度通常出现在 每次减少一半数据 的算法中。
最经典例子:
二分查找
例如在一个有序数组中找数字:
1 3 5 7 9 11 13
查找:
7
步骤:
1 取中间值 7
2 找到
如果找不到:
每次排除一半数据
例如:
100万数据
查找次数大约:
20 次
所以:
时间复杂度 = O(log n)
常见复杂度排序
算法复杂度从 快到慢:
O(1)
O(log n)
O(n)
O(n log n)
O(n²)
O(2^n)
O(n!)
实际开发中通常希望:
O(n log n) 以内
一个实际例子
问题:
判断数组中是否存在某个数字
方法1:遍历
func contains(_ arr: [Int], _ target: Int) -> Bool {
for num in arr {
if num == target {
return true
}
}
return false
}
时间复杂度:
O(n)
方法2:二分查找(数组有序)
func binarySearch(_ arr: [Int], _ target: Int) -> Bool {
var left = 0
var right = arr.count - 1
while left <= right {
let mid = (left + right) / 2
if arr[mid] == target {
return true
} else if arr[mid] < target {
left = mid + 1
} else {
right = mid - 1
}
}
return false
}
时间复杂度:
O(log n)
如何快速判断时间复杂度
经验总结:
一个循环
O(n)
嵌套循环
O(n²)
每次减少一半
O(log n)
循环 + 二分
O(n log n)
为什么时间复杂度重要
假设:
n = 1,000,000
不同算法执行次数:
算法 执行次数
O(1) 1 O(log n) 20 O(n) 1,000,000 O(n²) 1,000,000,000,000
差距会非常巨大。
本节总结
时间复杂度用于:
衡量算法效率
常见复杂度:
O(1)
O(log n)
O(n)
O(n log n)
O(n²)
理解时间复杂度后,我们才能:
写出更高效的算法
下一课
下一节我们进入 第一个数据结构:
数组(Array)
你将学会:
- 数组的底层原理
- 为什么数组访问是 O(1)
- 数组为什么插入很慢