大O复杂度表示法
求1,2,3,…,n的累加和。
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。k可以看出来,所有代码的执行时间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;
}
}
}
第2、3、4行都需要一个unit_time的时间。第5、6行代码循环执行了n遍,需要2n * unit_time的执行时间,第7、8行代码循环执行了n的次方遍,所以需要2n² * unit_time的执行时间。因此,整段代码的总的执行时间T(n) = (2n²+2n+3) * unit_time。
综上可知所有代码的执行时间T(n)与每行代码的执行次数n成正比。
我们可以把这个规律总结成一个公式。
T(n) = O(f(n))
其中T(n)表示代码执行的时间。n表示数据规模的大小。f(n)表示每行代码执行的次数总和。
因为这是一个公式,所以用f(n)表示。公式中的O,表示代码的执行时间T(n)与f(n)表达式成正比。
总结:第一个例子中T(n) = O(2n+2);第二个例子中T(n) = O(2n²+2n+3)。
这就是大O时间复杂度表示法。大O时间复杂度实际上并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势。因此,也叫渐进时间复杂度。
由于大O表示法只是一个变化趋势,因此可以忽略公式中的常量、低阶、系数,只需要记录最大阶的量级就可以了。
时间复杂度分析的方法:
1、只关注循环次数最多的一段代码。
如下面的一段代码,这段代码只需要看执行次数最多的一行代码,其中核心代码执行次数的n的量级,就是整段要分析代码的时间复杂度。
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相关,重点分析,。这两行代码被执行了n次,所以总的时间复杂度就是O(n)。
2、加法法则。
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;
}
分析上面代码,可以看出第一个for循环是100次,为常量级,直接忽略。第二个for循环为O(n)。第三个嵌套for循环为O(n²)。综合一下我们取最大量级的复杂度。因此时间复杂度为O(n²)。
3、乘法法则。
乘法法则就是嵌套循环的时候,把嵌套内外代码复杂度的乘积。
如下代码
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;
}
我们单独看cal()函数,假设f()只是一个普通的操作,那第4~6行的时间复杂度就是T1(n) = O(n)。但f()函数本身不是一个简单的操作,它的时间复杂度是T2(n) = O(n),所以,整个cal()函数的时间复杂度就是,T(n) = T1(n) * T2(n) = O(n * n) = O(n²)。
总结:复杂度量级
常量阶:O(1)
对数阶:O(log n)
线性阶:O(n)
线性对数阶:O(n log n)
平方阶:O(n²)
立方阶:O(n^3)
k次方阶:O(n^k)
指数阶级:O(2^n)
阶乘阶:O(n!)
其中对数阶和线性阶对数阶最常见也是最难分析的一种