最近练习算法题,又看了极客时间中的《数据结构与算法之美》写的真不错,于是总结一下关于复杂度的知识,代码和图片都是课程里面的。虽然是按课程写的,但是自己写一遍最好,否则看过就忘了。
数据结构与算法本身解决的是快和省的问题。如何让代码运行的更快,如何更省空间有个重要的标准:复杂度分析
算法的执行效率,粗略的讲就是算法代码执行的时间,在我们不做精确监控统计,靠观察怎么分析代码执行的时间呢?
int cal(int n) {
int sum = 0;--------2
int i = 1;--------3
for (; i <= n; ++i) {--------4
sum = sum + i;--------5
}
return sum;
}
上面这段代码是计算1到n的累加和,从CPU的角度来看每一行的执行都是:读数据-计算-写数据。尽管每行代码对应的CPU执行时间都不一样,做粗略计算的时候只需要估算,把这个时间看成相同的一个时间:unit_time。
2,3行代码分别需要1*unit_time,for循环里面的第4,5行需要n*unit_time,得到的结果是2*unit_time+2n*unit_time = (2n+2)*unit_time。得出一个结论:所有代码执行的时间T(n)与每行代码执行的时间unit_time成正比
按照这个方法看下面一段代码:
int cal(int n) {
int sum = 0;--------2
int i = 1;--------3
int j = 1;--------4
for (; i <= n; ++i) {--------5
j = 1;--------6
for (; j <= n; ++j) {--------7
sum = sum + i * j;--------8
}
}
}
2,3,4分别行执行了1次总共3*unit_time,5,6行分别执行了n*unit_time,7,8行分别执行了n^2*unit_time,总共是(2n^2+2n+3)*unit_time.尽管不知道*unit_time的具体值,但还是得到那个规律:代码执行的时间T(n)与每行代码执行的次数f(n)成正比
总结一个公式就是:
所以第一段代码的T(n) = O(2n+2),第二段代码的T(n) = O(2n^2+2n+3)这就是时间复杂度(asymptotic time complexity)的表示方法
而代码规模n很大时就可以忽略掉低阶,常量和系数,最终得到,第一段代码的T(n) = O(n),第二段T(n) = O(n^2)
就像上面第一段代码,我们只关注for循环的运行次数就可以得出这段代码的时间复杂度是T(n) = O(n)
这里有段代码,我们分析一下
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
很显然最高阶是第三部分,所以这段代码的时间复杂度是O(n^2)
看一段代码
int cal(int n) {
int ret = 0;
int i = 1;
for (; i < n; ++i) {
ret = ret + f(i);
}
}
int f(int n) {
int sum = 0;
int i = 1;
for (; i < n; ++i) {
sum = sum + i;
}
return sum;
}
for循环里面嵌入的方法f(x)的时间复杂度为O(n)外部for循环的复杂度是O(n)那么这段代码的时间复杂度就是O(n*n)=O(n^2)。其实还是跟上面分析的一样是两个否循环的嵌套跟2中那个最高阶的一样。
上面这些可以分两类,多项式量级和非多项式量级,其中费多项式量级只有划黄色下划线那两个。非多项式量级的算法问题叫作 NP(Non-Deterministic Polynomial,非确定多项式)问题。
按我们的数学知识,非多项式量级的时间复杂度会随数据规模n的增大而急剧增大,求解问题的执行时间会无线增长,所以非多项式量级的算法其实效率非常低。所以不多去分析了,只分析多项式量级算法。
看段代码
int i = 8;
int j = 6;
int sum = i + j;
这段代码的时间复杂度并不是O(3)而是O(1),只要代码的运行时间不随n的增大而增长我们都记作O(1),只要不存在递归和循环,再多行代码的时间复杂度都是O(1)。
i=1;
while (i <= n) {
i = i * 2;
}
不难看出这段代码的第三行的阶数最高,i的取值是个等比数列
x=如果把2换成3就有其实不管底是2,3还是10时间复杂度都可以记为O(logn)因为=(C * )其中C = 是个常数可以忽略,对数阶时间复杂度表示的时候可以省略调对数的底,统一写成O(logn)。理解了这个O(nlogn)其实就不难理解了,就是一个嵌套的乘法法则,一个循环嵌套一个时间复杂度为O(logn)的代码。比如归并排序和快速排序的时间复杂度都是O(nlogn)
代码的时间复杂度由两种数据的规模来决定
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 (; j < n; ++j) {
sum_2 = sum_2 + j;
}
return sum_1 + sum_2;
}
像这种我们无法预估m和n的量级谁的比较大,所以无法用加法法则忽略掉一个,所以表示为O(m+n),但乘法法则依然有效所以可以有O(m * n)
空间复杂度的全称是渐进空间复杂度(asymptotic space complexity),表示的是算法的存储空间跟数据规模的增长关系。看代码:
void print(int n) {
int i = 0;
int[] a = new int[n];
for (i; i = 0; --i) {
print out a[i]
}
}
代码中申请了一个长度为n的数组,其他地方没有占用到更多空间,所以空间复杂度就是O(n),常见的空间复杂度就是 O(1)、O(n)、O(n2 ),像 O(logn)、O(nlogn) 这样的对数阶复杂度平时都用不到。而且,空间复杂度分析比时间复杂度分析要简单很多。