函数调用的时间复杂度分析
我们来看下边这个例子:
int i, j;
for(i=0; i < n; i++) {
function(i);
}
void function(int count) {
printf(“%d”, count);
}
算法的时间复杂度为O(n^2)。
线性阶
•
一般含有非嵌套循环涉及线性阶,线性阶就是随着问题规模
n
的扩大,对应计算次数呈直线增长。
inti , n = 100, sum = 0;
for( i=0; i < n; i++ )
{
sum = sum + i;
}
•
上面这段代码,它的循环的时间复杂度为
O(n)
,因为循环体中的代码需要执行
n
次。
平方阶
•
刚才是单个循环结构,那么嵌套呢?
inti, j, n = 100;
inti, j, n = 100;
for( i=0; i < n; i++ )
{
for( j=0; j < n; j++ )
{
printf(“I love FishC.com\n”);
}
}
•n等于100,也就是说外层循环每执行一次,内层循环就执行100次,那总共程序想要从这两个循环出来,需要执行100*100次,也就是n的平方。所以这段代码的时间复杂度为O(n^2)。
•那如果有三个这样的嵌套循环呢?
•没错,那就是n^3啦。所以我们很容易总结得出,循环的时间复杂度等于循环体的复杂度乘以该循环运行的次数。
int i, j, n = 100;
for( i=0; i < n; i++ )
{
for( j=i; j < n; j++ )
{
printf(“I love FishC.com\n”);
}
}
•
分析下,由于当
i
=0
时,内循环执行了
n
次,当
i
=1
时,内循环则执行
n-1
次
……
当
i
=n-1
时,内循环执行
1
次,所以总的执行次数应该是:
–
n+(n-1)+(n-2)+…+1 = n(n+1)/2
•
大家还记得这个公式吧?恩恩,没错啦,就是搞死先生发明的算法丫。
•
那咱理解后可以继续,
n(n+1)/2 = n^2/2+n/2
•
用我们推导大
O
的攻略,第一条忽略,因为没有常数相加。第二条只保留最高项,所以
n/2
这项去掉。第三条,去除与最高项相乘的常数,最终得
O(n^2)
。
对数阶
•
我们看下这个程序:
int i = 1, n = 100;
while( i < n )
{
i = i * 2;
}
•
由于每次
i
*
2
之后,就举例
n
更近一步,假设有
x
个
2
相乘后大于或等于
n
,则会退出循环。
•
于是由
2^x = n
得到
x = log(2)n
,所以这个循环的时间复杂度为
O(
logn
)
。
常见的时间复杂度
•
常用的时间复杂度所耗费的时间从小到大依次是:
O(1) < O(
logn
) < (n) < O(
nlogn
) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(
n^n
)
•
O(1),O(
logn
),O(n),O(n^2)
我们前边已经给大家举例谈过了,至于
O(
nlogn
)
我们将会在今后的课程中介绍。
•
而像
O(n^3)
之后的这些,由于
n
值的增大都会使得结果大得难以想象,我们没必要去讨论它们。
最坏情况与平均情况
•
算法的分析也是类似,我们查找一个有
n
个随机数字数组中的某个数字,最好的情况是第一个数字就是,那么算法的时间复杂度为
O(1)
,但也有可能这个数字就在最后一个位置,那么时间复杂度为
O(n)
。
•
平均运行时间是期望的运行时间。
•
最坏运行时间是一种保证。在应用中,这是一种最重要的需求,通常除非特别指定,我们提到的运行时间都是最坏情况的运行时间。
•
我们在写代码时,完全可以用空间来换去时间。
•
举个例子说,要判断某年是不是闰年,你可能会花一点心思来写一个算法,每给一个年份,就可以通过这个算法计算得到是否闰年的结果。
•
另外一种方法是,事先建立一个有
2050
个元素的数组,然后把所有的年份按下标的数字对应,如果是闰年,则此数组元素的值是
1
,如果不是元素的值则为
0
。这样,所谓的判断某一年是否为闰年就变成了查找这个数组某一个元素的值的问题。