算法复杂度问题
引例
第一种算法:(1+(n+1)+n)=2n+2次
int i,sum=0,n=100; //执行1次
for(i=1;i<=n;i++){ // 执行n+1词
sum+=i; //执行n次
}
第二种算法: 1+1=2次
int sum=0,n=100; //执行1次
sum=(1+n)*n/2; //执行1次
如果我们把循环看作一个整体,忽略头尾判断的开销,那么这两个算法就是n和1的差距;
算法的复杂度,我们侧重的是研究算法随着输入规模的扩大增长量的一个抽象,而不是精确的定位执行了多少次;
列:
int i,j,x=0,sum=0,n=100;
for(i=1;i<=n;i++){ //执行n+1次
for(j=1;j<=n;j++){ //当外层i每执行1次,内层j执行n+1次
x++; //执行n次
sum=sum+x;
}
}
此算法的复杂度就为$n{2}$,也就是$1002$次;
我们在分析算法的运行时间时,重要的是把基本操作的数量和输入模式关联起来。
时间复杂度的定义: 在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况确定T(n)的数量级。
算法的时间复杂度,也就是算法的时间量度,记作:T(n)=O(f(n))。
它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度,简称为时间复杂度。其中f(n)是问题规模n的某个函数。
通用攻略:
用常数1取代运行时间中的所有加法常数。
在修改后的运行次数函数中,只保留最高阶项。
如果最高阶项存在且不是1,则除去与这个项相乘的常数。
得到的最后结果就是大O阶。
常数阶
int sum=o,n=100;
printf("第一条");
printf("第二条");
printf("第三条");
printf("第四条");
printf("第五条");
printf("第六条");
sum = (i+1)*n/2; //只有这一句影响了n
因为T(n)是关于问题规模n的函数,所以时间复杂度为:O(1) ;
线性阶
int n=100;
for(int i=1;i < n;i++){
printf("hello guys"); //执行n次
}
因为循环体中的代码需要执行n次,所以时间复杂度为:O(n) ;
平方阶(1)
int n=100;
for(int i=0;i < n;i++){ //执行n次
for(int j=0;j < n;j++){ //执行n*n次
printf("hello guys");
}
}
因为循环体中的代码需要执行n*n次,所以时间复杂度为:O($n^{2}$) ;
平方阶(2)
int n=100;
for(int i=0;i< n;i++){ //执行n次
for(int j=i;j< n;j++){ //当i=n时,j执行n-1次
printf("hello guys");
}
}
分析:
内循环总的执行次数应该是:
$-n+(n-1)+(n-2)+...+1=frac{n(n+1)}{2}$
$frac{n(n+1)}{2}=frac{n^2}{2}+frac{n}{2}$
根据大O攻略:
第一条忽略,因为没有常数相加。
第二条只保留最高项,所以$frac{n}{2}$去掉。
第三条,去除与最高项相乘的常数,最终得O($n^{2}$)。
对数阶
int n=100;
while(int i<n){
i=i*2;
}
由于$2{x}=n$得到$x=log_2{n}$,所以,这个循环的时间复杂度为O($log{n}$)。
函数时间复杂度分析
int n=100;
for(int i=0;i< n;i++){
function(i);
}
void function(int count){
printf("hello guys");
}
函数的复杂度是O(1),而for循环的调用,所以时间复杂度是O(n)。
int n++;
function(n);
for(int i=0;i< n;i++){
function(i);
}
for(int i=0;i< n;i++){
for(j=i;j< n;j++){
printf("hello guys");
}
}
void function(int count){
printf("hello guys");
}
$1$+$1$+$n$+$n{2}$=**O($n{2}$)**
总结:
O(1) < O($log{n}$) < (n) < O($nlog{n}$) < O($n^2$) < O($n^3$) < O($2^n$) < O($n!$) < O($n^n$)