1 、算法的时间性能分析
(1 )算法耗费的时间和语句频度
一个算法所耗费的时间= 算法中每条语句的执行时间之和
每条语句的执行时间= 语句的执行次数( 即频度(Frequency Count)) ×语句执行一次所需时间
算法转换为程序后,每条语句执行一次所需的时间取决于机器的指令性能、速度以及编译所产生的代码质量等难以确定的因素。
若要独立于机器的软、硬件系统来分析算法的时间耗费,则设每条语句执行一次所需的时间均是单位时间,一个算法的时间耗费就是该算法中所有语句的频度之和。
(1 )算法耗费的时间和语句频度
一个算法所耗费的时间= 算法中每条语句的执行时间之和
每条语句的执行时间= 语句的执行次数( 即频度(Frequency Count)) ×语句执行一次所需时间
算法转换为程序后,每条语句执行一次所需的时间取决于机器的指令性能、速度以及编译所产生的代码质量等难以确定的因素。
若要独立于机器的软、硬件系统来分析算法的时间耗费,则设每条语句执行一次所需的时间均是单位时间,一个算法的时间耗费就是该算法中所有语句的频度之和。
求两个n 阶方阵的乘积 C=A ×B, 其算法如下 :
# define n 100 // n 可根据需要定义, 这里假定为 100
void MatrixMultiply(int A[a] ,int B [n][n] , int C[n][n])
{ // 右边列为各语句的频度
int i ,j ,k;
(1) for(i=0; i < n;j++) n+1
(2) for (j=0;j < n;j++) { n(n+1)
(3) C[i][j]=0; n2
(4) for (k=0; k < n; k++) n2 (n+1)
(5) C[i][j]=C[i][j]+A[i][k]*B[k][j]; n3
}
}
该算法中所有语句的频度之和( 即算法的时间耗费) 为:
T(n)=2n3 +3n2 +2n+1 (1.1)
分析:
语句(1) 的循环控制变量i 要增加到n ,测试到i=n 成立才会终止。故它的频度是n+1 。但是它的循环体却只能执行n 次。语句(2) 作为语句(1) 循环体内的语句应该执行n 次,但语句(2) 本身要执行n+1 次,所以语句(2) 的频度是n(n+1) 。同理可得语句(3) ,(4) 和(5) 的频度分别是n2 ,n2 (n+1) 和n3 。
算法MatrixMultiply 的时间耗费T(n) 是矩阵阶数n 的函数。
(2 )问题规模和算法的时间复杂度
算法求解问题的输入量称为问题的规模(Size), 一般用一个整数表示。
# define n 100 // n 可根据需要定义, 这里假定为 100
void MatrixMultiply(int A[a] ,int B [n][n] , int C[n][n])
{ // 右边列为各语句的频度
int i ,j ,k;
(1) for(i=0; i < n;j++) n+1
(2) for (j=0;j < n;j++) { n(n+1)
(3) C[i][j]=0; n2
(4) for (k=0; k < n; k++) n2 (n+1)
(5) C[i][j]=C[i][j]+A[i][k]*B[k][j]; n3
}
}
该算法中所有语句的频度之和( 即算法的时间耗费) 为:
T(n)=2n3 +3n2 +2n+1 (1.1)
分析:
语句(1) 的循环控制变量i 要增加到n ,测试到i=n 成立才会终止。故它的频度是n+1 。但是它的循环体却只能执行n 次。语句(2) 作为语句(1) 循环体内的语句应该执行n 次,但语句(2) 本身要执行n+1 次,所以语句(2) 的频度是n(n+1) 。同理可得语句(3) ,(4) 和(5) 的频度分别是n2 ,n2 (n+1) 和n3 。
算法MatrixMultiply 的时间耗费T(n) 是矩阵阶数n 的函数。
(2 )问题规模和算法的时间复杂度
算法求解问题的输入量称为问题的规模(Size), 一般用一个整数表示。
矩阵乘积问题的规模是矩阵的阶数。
一个图论问题的规模则是图中的顶点数或边数。
一个算法的时间复杂度(Time Complexity, 也称时间复杂性)T(n) 是该算法的时间耗费,是该算法所求解问题规模n 的函数。当问题的规模n 趋向无穷大时,时间复杂度T(n) 的数量级( 阶) 称为算法的渐进时间复杂度。
一个算法的时间复杂度(Time Complexity, 也称时间复杂性)T(n) 是该算法的时间耗费,是该算法所求解问题规模n 的函数。当问题的规模n 趋向无穷大时,时间复杂度T(n) 的数量级( 阶) 称为算法的渐进时间复杂度。
算法MatrixMultidy 的时间复杂度T(n) 如(1.1) 式所示,当n 趋向无穷大时,显然有
这表明,当n 充分大时,T(n) 和n3 之比是一个不等于零的常数。即T(n) 和n3 是同阶的,或者说T(n) 和n3 的数量级相同。记作T(n)=O(n3 ) 是算法MatrixMultiply 的渐近时间复杂度。
![](http://erictb.blog.edu.cn/UploadFiles/2006-4/42440229.bmp)
这表明,当n 充分大时,T(n) 和n3 之比是一个不等于零的常数。即T(n) 和n3 是同阶的,或者说T(n) 和n3 的数量级相同。记作T(n)=O(n3 ) 是算法MatrixMultiply 的渐近时间复杂度。
(3 )渐进时间复杂度评价算法时间性能
主要用算法时间复杂度的数量级( 即算法的渐近时间复杂度) 评价一个算法的时间性能。
主要用算法时间复杂度的数量级( 即算法的渐近时间复杂度) 评价一个算法的时间性能。
算法MatrixMultiply 的时间复杂度一般为T(n)=O(n3 ) ,f(n)=n3 是该算法中语句(5) 的频度。下面再举例说明如何求算法的时间复杂度。
交换i 和j 的内容。
Temp=i;
i=j;
j=temp;
以上三条单个语句的频度均为1 , 该程序段的执行时间是一个与问题规模n 无关的常数。算法的时间复杂度为常数阶,记作T(n)=O(1) 。
注意:如果算法的执行时间不随着问题规模n 的增加而增长,即使算法中有上千条语句,其执行时间也不过是一个较大的常数。此类算法的时间复杂度是O(1) 。
变量计数之一:
(1) x=0;y=0;
(2) for(k-1;k<=n;k++)
(3) x++;
(4) for(i=1;i<=n;i++)
(5) for(j=1;j<=n;j++)
(6) y++;
一般情况下,对步进循环语句只需考虑循环体中语句的执行次数,忽略该语句中步长加1 、终值判别、控制转移等成分。因此,以上程序段中频度最大的语句是(6) ,其频度为f(n)=n2 ,所以该程序段的时间复杂度为T(n)=O(n2 ) 。
当有若干个循环语句时,算法的时间复杂度是由嵌套层数最多的循环语句中最内层语句的频度f(n) 决定的。
Temp=i;
i=j;
j=temp;
以上三条单个语句的频度均为1 , 该程序段的执行时间是一个与问题规模n 无关的常数。算法的时间复杂度为常数阶,记作T(n)=O(1) 。
注意:如果算法的执行时间不随着问题规模n 的增加而增长,即使算法中有上千条语句,其执行时间也不过是一个较大的常数。此类算法的时间复杂度是O(1) 。
变量计数之一:
(1) x=0;y=0;
(2) for(k-1;k<=n;k++)
(3) x++;
(4) for(i=1;i<=n;i++)
(5) for(j=1;j<=n;j++)
(6) y++;
一般情况下,对步进循环语句只需考虑循环体中语句的执行次数,忽略该语句中步长加1 、终值判别、控制转移等成分。因此,以上程序段中频度最大的语句是(6) ,其频度为f(n)=n2 ,所以该程序段的时间复杂度为T(n)=O(n2 ) 。
当有若干个循环语句时,算法的时间复杂度是由嵌套层数最多的循环语句中最内层语句的频度f(n) 决定的。
变量计数之二:
(1) x=1;
(2) for(i=1;i<=n;i++)
(3) for(j=1;j<=i;j++)
(4) for(k=1;k<=j;k++)
(5) x++;
该程序段中频度最大的语句是(5) ,内循环的执行次数虽然与问题规模n 没有直接关系,但是却与外层循环的变量取值有关,而最外层循环的次数直接与n 有关,因此可以从内层循环向外层分析语句(5) 的执行次数:
(1) x=1;
(2) for(i=1;i<=n;i++)
(3) for(j=1;j<=i;j++)
(4) for(k=1;k<=j;k++)
(5) x++;
该程序段中频度最大的语句是(5) ,内循环的执行次数虽然与问题规模n 没有直接关系,但是却与外层循环的变量取值有关,而最外层循环的次数直接与n 有关,因此可以从内层循环向外层分析语句(5) 的执行次数:
![](http://erictb.blog.edu.cn/UploadFiles/2006-4/42628013.bmp)
则该程序段的时间复杂度为T(n)=O(n3 /6+ 低次项)=O(n3 ) 。
(4 )算法的时间复杂度不仅仅依赖于问题的规模,还与输入实例的初始状态有关。
在数值A[0..n-1] 中查找给定值K 的算法大致如下:
(1)i=n-1;
(2)while(i>=0&&(A[i]!=k))
(3) i--;
(4)return i;
此算法中的语句(3) 的频度不仅与问题规模n 有关,还与输入实例中A 的各元素取值及K 的取值有关 :
①若A 中没有与K 相等的元素,则语句(3) 的频度f(n)=n ;
②若A 的最后一个元素等于K, 则语句(3) 的频度f(n) 是常数0 。
(5 )最坏时间复杂度和平均时间复杂度
最坏情况下的时间复杂度称最坏时间复杂度。一般不特别说明,讨论的时间复杂度均是最坏情况下的时间复杂度。
这样做的原因是:最坏情况下的时间复杂度是算法在任何输入实例上运行时间的上界,这就保证了算法的运行时间不会比任何更长。
平均时间复杂度是指所有可能的输入实例均以等概率出现的情况下,算法的期望运行时间。
(1)i=n-1;
(2)while(i>=0&&(A[i]!=k))
(3) i--;
(4)return i;
此算法中的语句(3) 的频度不仅与问题规模n 有关,还与输入实例中A 的各元素取值及K 的取值有关 :
①若A 中没有与K 相等的元素,则语句(3) 的频度f(n)=n ;
②若A 的最后一个元素等于K, 则语句(3) 的频度f(n) 是常数0 。
(5 )最坏时间复杂度和平均时间复杂度
最坏情况下的时间复杂度称最坏时间复杂度。一般不特别说明,讨论的时间复杂度均是最坏情况下的时间复杂度。
这样做的原因是:最坏情况下的时间复杂度是算法在任何输入实例上运行时间的上界,这就保证了算法的运行时间不会比任何更长。
平均时间复杂度是指所有可能的输入实例均以等概率出现的情况下,算法的期望运行时间。
常见的时间复杂度按数量级递增排列依次为:常数0(1) 、对数阶0(log2 n) 、线形阶0(n) 、线形对数阶0(nlog2 n) 、平方阶0(n2 ) 立方阶0(n3 ) 、…、k 次方阶0(nk ) 、指数阶0(2n ) 。
空间复杂度:
一个算法的空间复杂度(Space Complexity)S(n) 定义为该算法所耗费的存储空间,它也是问题规模n 的函数。渐近空间复杂度也常常简称为空间复杂度。算法的时间复杂度和空间复杂度合称为算法的复杂度。
补充:
1 、表示法:
设 f 和 g 是定义域为自然数集 N 上的函数
f (n )=O (g (n )).
若存在正数 c 和 n 0 使得对一切 n ≥ n 0 有 0 ≤ f (n ) ≤ cg (n )
f (n )= Ω (g (n )).
若存在正数 c 和 n 0 使得对一切 n ≥ n 0 有 0 ≤ cg (n ) ≤ f (n )
f (n )=o(g (n )).
对所有正数 c <1 存在 n 0 使得对一切 n ≥ n 0 有 0 ≤ f (n )<cg (n )
f (n )= Θ (g (n ))
f (n )=O (g (n )) 且 f (n )= Ω (g (n ))
O (1) 表示常数函数
解释:
Θ类似于 = ,表示两个函数具有相同的数量级;Ο类似于 <= ,表示我们的函数不会超过给出的特定函数的数量级;Ω类似于 >= ,表示我们的函数至少有给出的特定函数的数量级;ο类似于小于,表示我们的函数一定比给出的函数数量级小;ω类似于 > ,表示我们的函数一定比给出的特定函数数量级大(我们写 f(n) = Ω (g (n) ) , f(n) 就是我们所想要分析的函数, g(n) 就是一个给出的特定函数)。
大 O 表示法的常用计算规则:
加法规则
T (n ) = T 1(n )+ T 2(n ) = O (f 1(n )) + O (f 2(n ))=O (max(f 1(n ), f 2(n )))
乘法规则
T (n ) = T 1(n ) × T 2(n ) = O (f 1(n )) × O (f 2(n ))=O (f 1(n ) × f 2(n ))
2 、基线条件(base case ) :
int factorial(int n)
{
return n * factorial(n - 1);
}
|
不过,这个函数的问题是,它会永远运行下去,因为它没有终止的地方。函数会连续不断地调用 factorial 。当计算到零时,没有条件来停止它,所以它会继续调用零和负数的阶乘。因此,我们的函数需要一个条件,告诉它何时停止。
由于小于 1 的数的阶乘没有任何意义,所以我们在计算到数字 1 的时候停止,并返回 1 的阶乘(即 1 )。因此,真正的递归函数类似于:
int factorial(int n)
{
if(n == 1)
{
return 1;
}
else
{
return n * factorial(n - 1);
}
}
|
可见,只要初始值大于零,这个函数就能够终止。停止的位置称为 基线条件(base case ) 。基线条件是递归程序的最底层位置,在此位置时没有必要再进行操作,可以直接返回一个结果。所有递归程序都必须至少拥有一个基线条件,而且必须确保它们最终会达到某个基线条件;否则,程序将永远运行下去,直到程序缺少内存或者栈空间。