数据结构和算法本身解决的是快和省的问题
衡量执行效率的就需要时间空间复杂度分析
复杂度分析是整个算法学习的精髓,只有掌握了他,数据结构和算法的内容就基本掌握了一半。
1)测试结果依赖测试环境
2)测试结果受数据规模的影响很大
所以结合上述两种情况,就需要一个不用具体的测试数据来测试,就可以粗略的估算算法的执行效率的方法。也就是时间空间复杂度。
从CPU的角度来看,每一行代码的操作都是读数据-运算-写数据
即,可以把一行代码的时间看作一个 unit_time时间单位
如下
例子1
public class HelloWorld {
public static void main(String[] args) {
}
public static int cal(int n){
int sum = 0; // unit_time 一个时间单位
int i = 1; // unit_time 一个时间单位
for (; i <= n; ++i) { // unit_time * n n个时间单位
sum = sum + 1; // unit_time * n n个时间单位
}
return sum;
}
}
上述时间单位综合为 1+1+n+n
即(2n+2)* unit_time
由此,所有代码的执行时间T(n)与每行代码的执行次数成正比
依照上述思路 看下个
例子2
public static int cal(int n){
int sum = 0; // unit_time 一个时间单位
int i = 1; // unit_time 一个时间单位
int j = 1; // unit_time 一个时间单位
for (; i <= n; ++i) { // unit_time * n n个时间单位
j=1; // unit_time * n n个时间单位
for (; j <= n; ++j) { // unit_time * n * n n2个时间单
sum = sum + i * j; // unit_time * n * n n2个时间单位
}
}
return sum;
}
所以以上的总时间就是1+1+1+n+n+n2+n2 = 3+2n+2n2
即总共的耗时是3+2n+2n2个时间单位。
以上可以得出 所有代码的执行时间T(n)与每行代码的执行次数成正比。
总结公式就是
T(n)= O(f(n))
T(n) :代码执行所需要的时间
f (n):表示每行代码执行的次数总和。
O表示代码执行时间和次数总和是成正比的。即需要时间越长,执行的次数越多。
例子1 2n+2
例子2 3+2n+2n2
公式化:T(n) = O(2n+2)
公式化:T(n) = O(3+2n+2n2)
以上就是大O表示法了,大O表示法表示的是 代码执行时间随数据规模增长的变化趋势
在公式中低阶,常量,系数三部分并不左右增长趋势。只需要记录一个最大量级就可以了。
公式化:T(n) = O(2n+2)
公式化:T(n) = O(3+2n+2n2)
以上公式化内容写成以下即可
T(n) = O(n);
T(n) = O(n2);
大O表示法,表示的是趋势,同时会省略掉公式中的常量,低阶,系数,并且只需要记录最大量级。
分析一个算法,代码就是只关注循环次数最多的代码那一段代码就可以了。这一段最多的执行次数的量级就是这段代码的时间复杂度
例子1
public static int cal(int n) {
int sum = 0;
int i = 1;
for (; i <= n; ++i) {
sum=sum+1;
}
return sum;
}
以上例子执行次数最多的就是for循环,与n的大小无关。for循环执行了n次,所以这段代码的时间复杂度就是O(n);
例子1
public static int cal(int n) {
int sum_1 = 0;
int p = 1;
for (; p < 100; ++p) {
sum_1 = sum_1 + p;
}
int sum_2 = 0;
int q = 1;
for (; q < n; ++q) {
sum_2 = sum_2 + q;
}
int sum_3 = 0;
int i = 1;
int j = 1;
for (; i <= n; ++i) {
j = 1;
for (; j <= n; ++j) {
sum_3 = sum_3 + i * j;
}
}
return sum_1 + sum_2 + sum_3;
}
以上代码分为三个部分
sum_1 部分 sum_2部分 sum_3部分
sum_1 循环执行100次常量级 T(n) = O(n);
sum_2 循环执行n次常量级 T(n) = O(n);
sum_3 循环执行n2次指数级 T(n) = O(n2);
取出最大量级,所以例子1的时间复杂度就是O(n2);
抽象成公式:
T1(n) = O(f(n)) , T2(n) = O(g(n)) 两个代码执行时间 等于整个方法的执行时间
T(n) = T1(n) + T2(n)
但是取最大的时间作为时间复杂度
即T(n) = max(O(f(n)),O(g(n)))
若假设T1(n) = O(n),T2(n) = O(n2) 则 T1(n) * T2(n) = O(n3)
在代码中的体现就是嵌套循环
例子1
public static int cal(int n) {
int ret = 0;
int i = 1;
for (; i < n; ++i) {
ret = ret + f(i);
}
return ret;
}
private static int f(int n) {
int sum = 0;
int i = 1;
for (; i < n; ++i) {
sum = sum + 1;
}
return sum;
}
上述代码在循环中调用f方法 f方法的本身的时间复杂度是O(n)
cal如果不看f方法,时间复杂度也是O(n) 但是在cal中调用了f
所以整个cal方法的时间复杂度就是Tcal(n) *Tf(n) = O(n2)
复杂度量级 | 公式 |
---|---|
常量级 | O(1) |
对数级 | O(logn) |
线性级 | O(n) |
线性对数级 | O(nlogn) |
平方阶 | O(n2) |
立方阶 | O(n3) |
K次方阶 | O(nk) |
指数阶 | O(2n) |
阶乘阶 | O(n!) |
以上只有指数阶,阶乘阶,是非多项式量级
其余的都是多项式量级
时间复杂度是非多项式量级叫做NP问题。
因为随着n的规模增加,执行时间会急剧增加,执行时间会无限增长。
一般情况下,只要算法中不存在循环语句,递归语句,即便成千上万行代码,时间复杂度都是O(1)常量阶
例子1
public static int cal(int n) {
int i = 1;
while (i <= n) {
i = i * 2;
}
return i;
}
以上代码循环次数最多的是while
i = 2 +2平方 + 2立方 + …2x
求x = log2n
即上述时间复杂度就是O(logn)
这种时间复杂度与上述的不同,他的代码的复杂度,是由两个数据的规模来决定的
例子1
public static int cal(int m, int n) {
int sum_1 = 0;
int i = 1;
for (; i < m; ++i) {
sum_1 = sum_1 + i;
}
int sum_2 = 0;
int j = 1;
for (; i < n; ++i) {
sum_2 = sum_2 + i;
}
return sum_1 + sum_2;
}
例子中传入m和n两个数据规模,没有办法评估谁大谁小。
所以上述代码的时间复杂度就是O(m+n)
针对上述情况,原来的加法法则不正确,公式改为O(f(m)+g(n)).
从 低 到 高
O(1),O(logn),O(n),O(nlogn),O(n2)。