时间复杂度
计算时间复杂度通常需要两个步骤:1.根据(或推断)算法函数的时间增长趋势,得出其操作数函数。2.判断操作数函数的渐进上界。
1. 时间增长趋势
TIP
通常将线性时间复杂度记为:O(n),表示标识算法操作次数的函数的*渐进上界*。因此想获得算法的时间复杂度,需要先计算出算法的操作数函数T(n)。
🎉一些推算操作数的技巧:
- 忽略T(n)中的常数项。因为它们都与n无关,所以对时间复杂度不产生影响。
- 省略所有系数。例如,循环2n次、 3n+1次等,都可以简化记为n次。
- 循环嵌套时使用乘法。总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别套用第 1. 点和第 2. 点的技巧。
rust
fn algorithm(n: i32) {
let mut a = 1; // +0(技巧 1)
a = a + n; // +0(技巧 1)
// +n(技巧 2)
for i in 0..(5 * n + 1) {
println!("{}", 0);
}
// +n*n(技巧 3)
for i in 0..(2 * n) {
for j in 0..(n + 1) {
println!("{}", 0);
}
}
}
2. 判断渐进上界
TIP
时间复杂度由T(n)中最高阶的项来决定,应为当n趋于无穷大是,最高阶对时间增长的趋势的影响是其他项所不能相比的。
一些操作数对应的时间复杂度
操作数 | 时间复杂度 |
---|---|
1000000 | O(1) |
2n | O(n) |
3n+2 | O(n) |
n^2 + 2n +1 | o(n^2) |
n^3 + 2n^2 + n + 10 | O(n^3) |
2^n + 10000n^2 | O(2^n) |
因此该算法的的时间复杂度:
3. 常见时间复杂度类型
时间复杂度类型是根据输入数据n的大小与算法执行时间(趋势)的变化来决定的。
3.1 常数阶O(1)
当算法的运行时间跟输入数据n无关时,算法属于常数阶。
rust
fn alog(n: i32) -> i32 {
println!("{}", 0); //打印函数的执行时间固定,因此该算法的时间复杂度是常数阶。
}
fn alog(n: i32) -> i32 {
for i in 0..1000 {
println!("{}", i); //虽然打印程序需要循环1000次,但打印函数的执行时间固定,与输入数据n无关,因此该算法的时间复杂度是常数阶。
}
}
3.2 对数阶O(log n)
当算法的运行时间跟输入数据n的log成正比时,算法属于对数阶。
rust
/* 对数阶(循环实现) */
fn logarithmic(mut n: i32) -> i32 {
let mut count = 0;
while n > 1 {
n = n / 2;
count += 1;
}
count
}
3.3 线性阶O(n)
当算法的运行时间跟输入数据n成正比时,算法属于线性阶。
rust
fn linear(n: i32) -> i32 {
let mut count = 0;
for i in 0..n {
count += 1
}
}
3.4 线性对数阶
线性对数阶常出现于嵌套循环中,两层循环的时间复杂度分别为 O(n)和O(log n) 相关代码如下:
rust
/* 线性对数阶 */
fn linear_log_recur(n: i32) -> i32 {
if n <= 1 {
return 1;
}
let mut count = linear_log_recur(n / 2) + linear_log_recur(n / 2);
for _ in 0..n as i32 {
count += 1;
}
return count;
}
3.5 平方阶
平方阶的操作数量相对于输入数据大小n以平方级别增长。平方阶通常出现在嵌套循环中,外层循环和内层循环的时间复杂度都为O(n)。因此总体的时间复杂度为:O(n^2)
rust
fn square(n: i32) -> i32 {
let mut count = 0;
for i in 0..n {
for j in 0..n {
count += 1;
}
}
}
3.6 指数阶
生物学的“细胞分裂 ”是指数阶增长的典型例子:初始状态为1个细胞,分裂一轮后变为2个 ,分裂两轮后变为4个,以此类推,分裂n轮后有 2^n个细胞。时间复杂度为2^n
rust
/* 指数阶(循环实现) */
fn exponential(n: i32) -> i32 {
let mut count = 0;
let mut base = 1;
// 细胞每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1)
for _ in 0..n {
for _ in 0..base {
count += 1
}
base *= 2;
}
// count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1
count
}
3.7 阶乘
阶乘阶对应数学上的“全排列 ”问题。给定 n互不重复的元素,求其所有可能的排列方案,方案数量为:
rust
/* 阶乘阶(递归实现) */
fn factorial_recur(n: i32) -> i32 {
if n == 0 {
return 1;
}
let mut count = 0;
// 从 1 个分裂出 n 个
for _ in 0..n {
count += factorial_recur(n - 1);
}
count
}