先上代码举个例子
int cal(int n) {
int sum = 0;
int i = 1;
for (; i <= n; ++i) {
sum = sum + i;
}
return sum;
}
分析:我们假设每行代码执行的时间都一样,为unit_time,在这个假设的基础上,这段代码的总执行时间是多少呢?
第 2、3 行代码分别需要 1 个 unit_time 的执行时间,第 4、5 行都运行了 n 遍,所以需要 2n*unit_time 的执行时间,所以这段代码总的执行时间就是 (2n+2)*unit_time。可以看出来,所有代码的执行时间 T(n) 与每行代码的执行次数成正比。
我们再来看一段代码
int cal(int n) {
int sum = 0;
int i = 1;
int j = 1;
for (; i <= n; ++i) {
j = 1;
for (; j <= n; ++j) {
sum = sum + i * j;
}
}
}
我们依旧假设每个语句的执行时间是unit_time,那么这段代码的总执行时间T(n)是多少呢?
第 2、3、4 行代码,每行都需要 1 个 unit_time 的执行时间,第 5、6 行代码循环执行了 n 遍,需要 2n * unit_time 的执行时间,第 7、8 行代码循环执行了 n 2 遍,所以需要 2n2 * unit_time的执行时间。所以整段代码总的执行时间为:T(n) = (2n2+2n+3) * unit_time。
通过这两个例子我们推导出一个规律
所有代码的执行时间 T(n) 与每行代码的执行次数 n 成正比。
把这个规律总结成一个公式,此时,大O就登场了~
T(n) = O ( f (n) )
具体解释一下这个公式:T(n)表示代码执行的时间,n表示数据规模的大小,f(n)表示每行代码执行的次数总和。公式中的O,表示代码的执行时间T(n)与f(n)表达式成正比。
大O时间复杂度实际上并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势,所以也叫作渐进时间复杂度,简称时间复杂度。
我们在分析一个算法、一段代码的时间复杂度的时候,只关注循环执行次数最多的那一段代码就可以了。
依然是分析之前的那段代码
int cal(int n) {
int sum = 0;
int i = 1;
for (; i <= n; ++i) {
sum = sum + i;
}
return sum;
}
其中第 2、3 行代码都是常量级的执行时间,与 n 的大小无关,所以对于复杂度并没有影 响。循环执行次数最多的是第 4、5 行代码,所以这块代码要重点分析。这两行代码被执行了 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的时间复杂度跟n的规模无关。
sum_2的时间复杂度是O(n)。
sum_3的时间复杂度是O(n2)。
取最大的量级,整段代码的时间复杂度为O(n2)。
总的时间复杂度等于量级最大的那段代码的时间复杂度。
用公式表示:
如果T1(n) = O( f (n) ),T2(n) = O ( g(n) ),那么T(n) = T1(n) + T2(n) = max(O( f(n) ), O( g(n) ) ) = O( max(f(n), g(n)) )。
公式:如果 T1(n)=O( f(n) ),T2(n)=O( g(n) ),那么T(n) = T1(n) * T2(n) = O( f(n) ) * O( g(n) ) = O(f(n) * g(n))。
举个栗子
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;
}
假设f(n)只是一个普通的操作,那么cal()里面的for循环的时间复杂度就是T1(n) = O(n),但是f(n)的时间复杂度是T2(n) = O(n),所以整个cal()函数的时间复杂度是T(n) = T1(n) * T2(n) = O(n * n) = O( n 2)。
接下来是几种常见的多项式时间复杂度。
O(1)只是常量级时间复杂度的表示方法,并不是只执行了一行代码。
举个时间复杂度为O(1)的栗子
int i = 2;
int j = 3;
int sum = i + j;
一般情况下,只要算法中不存在循环语句、递归语句,即使有成千上万行的代码,其时间复杂度也是O(1)。
举个栗子
i=1;
while (i <= n) {
i = i * 2;
}
此时间复杂度为O(logn)
在采用大O标记复杂度的时候,可以忽略系数,即O(Cf(n)) = O(f(n))。
如果一段代码的时间复杂度是O(f(n)),我们循环执行了n遍,时间复杂度就是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;
}
这段代码的时间复杂度是O(m+n)。
针对这种情况,原来的加法法则则变为:T1(m)+T2(n)=O(f(n) + g(n))。但是乘法法则依然有效:T1(m)*T2(n) = O(f(n) * g(n))。
空间复杂度全称就是渐进空间复杂度,表示算法的存储空间与数据规模之间的增长关系。
举个栗子
void print(int n) {
int i = 0;
int[] a = new int[n];
for (i; i <n; ++i) {
a[i] = i * i;
}
for (i = n-1; i >= 0; --i) {
print out a[i]
}
}
第二行代码中,申请了一个空间存储变量i,但是它是常量阶的,与数据规模n无关,第三行申请了一个大小为n的int型数组,除此之外剩下的代码都没有占多少空间,所以整段代码的空间复杂度是O(n)。
常见的空间复杂度就是O(1),O(n),O(n2),对数阶复杂度基本用不上。
复杂度也叫渐进复杂度,包括时间复杂度和空间复杂度,用来分析算法执行效率与数据规模 之间的增长关系,可以粗略地表示,越高阶复杂度的算法,执行效率越低。常见的复杂度并 不多,从低阶到高阶有:O(1)、O(logn)、O(n)、O(nlogn)、O(n2)。