算法时间复杂度:在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。算法的时间复杂度,也就是算法的时间度量,记作:T(n)=O(f(n))。它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度,简称为时间复杂度。其中f(n)是问题规模n的某个函数。
用大写的O()来体现算法时间复杂度的记法,称之为大O记法。
一般情况下,随着输入规模n的增大,T(n)增长最慢的算法为最优算法。
推导大O阶的方法:
1)用常数1取代运行次数函数中的所有加法常数。
2)在修改后的运行次数函数中,只保留最高阶项。
3)如果最高阶项存在且不是1,则去除与这个项相乘的常数。
4)得到的最后结果就是大O阶
举例1:
int sum = 0, n = 100;
printf("Hello World!\n");
printf("Hello World!\n");
printf("Hello World!\n");
printf("Hello World!\n");
printf("Hello World!\n");
printf("Hello World!\n");
sum = (1+n)*n/2;
这段代码的大O是O(1),而不是O(8),因为T(n)是关于问题规模n的函数,但这段代码的运行次数,和问题规模n毫无关系,所以要记作O(1)。
牢记,如果大O内部是一个常数,那只能是1,而不能是别的数字。
线性阶:线性阶就是随着问题规模n的扩大,对应计算次数呈直线增长。一般含有非嵌套循环的程序涉及线性阶。
举例2:
int i , n = 100 , sum = 0;
for( i=0; i < n; i++ )
{
sum = sum + i;
}
上面这段代码,它的时间复杂度为O(n),因为循环体中的代码需要执行n次。平方阶,一般嵌套循环的程序涉及平方阶。
int i, j, n = 100;
for( i=0; i < n; i++ )
{
for( j=0; j < n; j++ )
{
printf("Hello World!\n");
}
}
上面这段代码,它的时间复杂度为O(n^2),如果是类似这样的三层嵌套,那就是O(n^3),所以我们容易得出结论:循环的时间复杂度等于循环体的复杂度乘以该循环运行的次数。
举例3:
int i, j, n = 100;
for( i=0; i < n; i++ )
{
for( j=i; j < n; j++ )
{
printf("Hello World!\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这项去掉,式子只剩下n^2/2。再根据第三条,去除与最高项相乘的常数,即去掉1/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)。