时间复杂度分析学习
目录
一. 大O复杂度表示法
每一行代码在CPU中都有类似的操作 读数据-运算-写数据,每个指令的CPU执行时间忽略不计
例一
//运算: 值加载到CPU寄存器->CPU执行: CPU每次执行的时间都是1
int cal(int n) {
int sum = 0; //给sum赋值 1
int i = 1; //给i赋值 1
for (; i <= n; ++i) { //会遍历n次 n
sum = sum + i; //执行n次 n
}
return sum;
}
执行次数 : 2n+2
例二
int cal(int n) {
int sum = 0;//1
int i = 1; //1
int j = 1; //1
for (; i <= n; ++i) { //n
j = 1; //n
for (; j <= n; ++j) { //n*n
sum = sum + i * j; //n*n
}
}
}
执行次数 : 2n*n+2n+3
\[ {a}
T(n) : 所有代码执行总时间;
\]
\[f(n) : 代码执行次数的总和
\]
\[时间复杂度: O= \frac{T(n)}{(f(n))}
\]
\[O表示代码的实行时间T(n)和总执行次数f(n)成正比
\]
\[T(n)=O(f(n))
\]
例一中的代码执行时间为: \(T(n)=O(2n+2)\),
第二个例子就是 \(T(n)=O(2n^2+2n+3)\)
(\(n\) 表示数据规模的大小)
这就是 大O时间复杂度表示法。 大O时间复杂度实际上并不是具体表示代码的真正执行时间,而是表示代码执行时间随着数据规模增长的变化趋势,也叫做 渐进时间复杂度
二. 时间复杂度分析法则
代码最大执行时间,加上\(n\)就代表一种随\(n\)变化的趋势:
1.单段代码看高频,多段代码取最大
只关注循环执行次数中最多的一段代码
大O复杂度表示法只是表示出一种变化的趋势,一般忽略掉公式中的 常量,低阶,系数,只需记录最大阶的量级即可(\(n|n^n\));
所以以上两个例子中的\(T(n)\)分别为:\(O(n)\) 和 \(O(n^2)\)
2. 多个规模求加法
最终的结果还是要满足单段代码看高频总复杂度等于量级最大的那段代码的复杂度
int cal(int n) {
int sum_1 = 0;
int p = 1;
for (; p < 100; ++p) { //100
sum_1 = sum_1 + p;
}
int sum_2 = 0;
int q = 1;
for (; q < n; ++q) { //n
sum_2 = sum_2 + q;
}
int sum_3 = 0;
int i = 1;
int j = 1;
for (; i <= n; ++i) { //n
j = 1;
for (; j <= n; ++j) { //n*n
sum_3 = sum_3 + i * j;
}
}
return sum_1 + sum_2 + sum_3;
}
忽略常数,只拿最大量,f(n)= n*n+2n
时间复杂度:
\[T(n)=O(n^2+2n) = O(n^2+n)=O(n^2)
\]
3. 嵌套代码求乘积
嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
for (; i <= n; ++i) { //n
j = 1;
for (; j <= n; ++j) { //如果不考虑外层for循环,单独这里的执行次数也是n
sum_3 = sum_3 + i * j;
}
}
所以执行次数为 f(n)=n*n
时间复杂度:
\[T(n)=O(n^2)
\]
-
总结:
-
- 单段代码看高频:比如循环
- 多段代码取最大:比如一段代码中有单循环和多重循环,那么取多重循环的复杂度。
- 嵌套代码求乘积:比如递归、多重循环等
- 多个规模求加法:比如方法有两个参数控制两个循环的次数,那么这时就取二者复杂度相加
三. 常见复杂度
多项式量级 | 非多项式量级 |
---|---|
常数阶\(O(1)\) | 指数阶\(O(2^n)\) |
对数阶\(O(logn)\) | 阶乘阶\(O(n^n)\) |
线性阶\(O(n)\) | |
线性对数阶\(O(nlogn)\) | |
k次方阶\(O(n^k)\) |
学过数学的都知道,非多项式量级的变化趋势会随着\(n\)的变大而急速增大
1.\(O(1)\)
int i = 8;
int j = 6;
int sum = i + j;
即便有3行,他的复杂度也是\(O(1)\)而不是\(O(3)\),满足法则1:只关注循环执行次数中最多的一段代码
2.\(O(n)\)
for (; j <= n; ++j) { //n*n
sum_3 = sum_3 + i * j;//n*n
}
每行要运行的次数,复杂度为\(O(n)\) 满足法则1
3.\(O(logn)\)
i=1;
while (i <= n) {
i = i * 2;
}
复习 \(: 2要乘自己3次能等于8,也就是2^3=8\)
\[i为一个常数,这的第三行代码一直在做2*2*2*2*2..的动作,一直乘到值>=n为止
\]
\[这里第三行假设2要乘自己x次才能和n相等,得出公式为2^x=n
\]
\[循环在2^x=n的时候结束,
\]
\[x为执行次数,所以 代码的执行次数f(n) = x
\]
\[根据高中数学,和公式2^x=n,算出x=log2^n
\]
高中对数学习
\[T(n)=O(log2^n) 去掉底数,常数,保留最大量阶 T(n)=O(logn)
\]
4. \(O(nlogn)\)
根据乘法法则,如果一个时间复杂度为\(O(logn)\)的代码中嵌套了另一段\(O(logn)\)的代码,那么这一整段的代码时间复杂度为
\[O(nlogn)=O(log{n^2})=O(logn)*O(logn)
\]