时间复杂度入门理解
前言
当你编写完一个程序的时候,怎样对它进行算法最优的判断呢?效率又是怎样体现的呢?效率=总执行次数/总时间,一般来说,程序越庞大,其执行效率越低。因此,对于模块化程序,优化其算法的时间复杂度是非常重要的。
定义
我们把一个算法中的语句执行次数定义为频度,算法的运行时间刻画为一个函数,定义为 T(n) ,其中n称为问题的规模,当n不断变化时,T(n)也随之变化。但我们想知道n与T(n)之间的大致规律,所以我们引入渐进符号 O 来刻画算法的运行时间。
现在我们编写一个程序,把其中表示算法运行时间的函数记为 T(n)。对于一个给定的函数 g(n),有 O(g(n)) = {f(n) | 存在常量 c, n0, c>0, n0>0, 使得对所有 n ≥ n0, 有0 ≤ f(n) ≤ c·g(n)},记作 f(n) = O(g(n)) 。如下图。注意这个等号并不是左右相等,而是代表集合论中的 “∈”,表示 'is a ..' 的关系,如“ n = O(n2) ”。当我们说运行时间为 O(g(n)) 时,表示存在一个O(g(n)) 的函数 f(n),使得 n 不管输入什么值时,T(n) 的上界都是 f(n)。所以 O(g(n)) 表示最坏情况运行时间。
通常,我们称 O(g(n)) 为时间复杂度。
图(a) f(n) = O(g(n))
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
// do .....
}
}
下面拿几道题练练手:
(1)
for(i=1;i<=n;i++) for(j=1;j<=n;j++) s++; //循环了n*n次,当然是O(n^2)
(2)
for(i=1;i<=n;i++) for(j=i;j<=n;j++) s++; //循环了(n+n-1+n-2+...+1)≈(n^2)/2,因为时间复杂度是不考虑系数的,所以也是O(n^2)
(3)
for(i=1;i<=n;i++) for(j=1;j<=i;j++) s++; //循环了(1+2+3+...+n)≈(n^2)/2,当然也是O(n^2)
(4)
i=1;k=0; while(i<=n-1){ k+=10*i; i++; } //循环了n-1≈n次,所以是O(n)
(5)
for(i=1;i<=n;i++) for(j=1;j<=i;j++) for(k=1;k<=j;k++) x=x+1; //循环了(1^2+2^2+3^2+...+n^2)=n(n+1)(2n+1)/6(这个公式要记住哦)≈(n^3)/3,不考虑系数,自然是O(n^3)
需要注意的是,在时间复杂度中,log(2,n)(以2为底)与lg(n)(以10为底)是等价的,因为对数换底公式:
在各种不同算法中,若算法中语句执行次数为一个常数,则时间复杂度为O(1),譬如简单的求和,代码如下,其频度为3,O(1)
int a,b; //频度为2
printf("%d",a+b); //频度为1
return 0;
实践出真知,下面放一些更难的例题,帮助理解与计算时间复杂度
while(n!=0)
{
n/=10;
}
while(n!=0)
{
n=n/2;
}
void func(int n) { int i=0,s=0; while(s<n) { i++; s=s+i; } }
时间复杂度O(n^(1/2))
解析:
测试样例: n = 3,5,9,...n^2 ,可得频度 N = 2,3,4,...n^(1/2) (近似计算)
则运行时间 T(n) = c*(n^(1/2)) + c2 (c, c2为常数),可得时间复杂度 O(n^(1/2))x=91; y=100; while(y>0) { if(x>100) { x=x-10; y--; } else x++; }