目录
1.3 空间复杂度 ( Space Complexity )
1. 算法复杂度分析总结
复杂度分析,它几乎占了数据结构与算法这门课的半壁江山,是数据结构和算法的精髓。时间复杂度和空间复杂度的分析有助于我们产出高质量的代码,能区分一流工程师和三流的工程师。
我们可以粗略地把复杂度分析分为两类,多项式量级和非多项式量级。其中非多项式量级只有两个:O()和 O( N ! )。
我们把时间复杂度为非多项式量级的算法问题叫做 NP(Non-Deterministic Polynomial, 非确定多项式)问题。当数据规模 N 越来越大时,非多项式量级算法执行时间会急剧增加,求解问题的执行时间会无限增长。所以,非多项式时间复杂度的算法其实是非常低效的。
1.1 复杂度分析法则
加法法则:如果 T1 ( N ) = O ( f( N )) 且 T2 ( N ) = O ( g(n) ),
那么,T1 ( N ) + T2 ( N ) = max ( O ( f( N ) ,O ( g(n) ) )。
即,总复杂度等于量级最大的那段代码的复杂度。
乘法法则:如果 T1 ( N ) = O ( f( N )) 且 T2 ( N ) = O ( g(n) ),
那么,T1 ( N ) * T2 ( N ) = O ( f( N ) * O ( g(n) )。
即,嵌套代码的复杂度等于嵌套内外代码复杂度的乘积。
优先法则:只关注循环执行次数最多的一行代码。
1.2 时间复杂度 ( Time Complexity )
1.2.1 常数阶 O(1)
O (1) 只是常量级时间复杂度的一种表示方法,并不是指只执行了一行的代码。比如这段代码,即便只有 3 行,它的时间复杂度是也是 O ( 1 ),而不是 O ( 3 )。
int i = 3;
int j = 3;
int sum = i + j;
只要代码的执行时间不随 N 的增大而增长,这样代码的时间复杂度我们都记作 O ( 1 )。或者说,一般情况下,只要算法中不存在循环语句和递归语句,即使有成千上万行的代码,其时间复杂度也是 O ( 1 )。
1.2.2 对数阶 O (log N)
对数阶时间复杂度非常常见,同时也是最难分析的一种时间复杂度。我通过一个例子来说明一下。
i = 1;
while( i <= n )
{
i = i * 2;
}
根据我们前面讲的复杂度分析算法,第三行代码是循环次数最多的。所以,我们只要能计算出这行代码被执行了多少次,就能知道整段代码的时间复杂度。
从代码中可以看出,变量 i 的值从 1 开始取,每循环一次就乘以 2。当 i > n 时,循环结束。还记记得高中学过的等比数列吗?实际上,变量 i 的取值就是一个等比数列。如果我把他一个一个列出来,就应该是这个样子的:
所以,我们只要知道 X 的值,就知道这行代码执行的次数,从而算出整段代码的时间复杂度。故 求解 X ,解出
,即这段代码的时间复杂度就是
。
现在,我把代码稍微改一下,你在看看,这段代码的时间复杂度是多少?
i = 1;
while( i <= n)
{
i = i * 3;
}
根据刚刚讲的思路,很简单就能看出来,这段代码的时间复杂度为 。
由对数换底公式可得:
所以 , 其中
是一个常量。在采用大 O 标记复杂度的时候,可以忽略系数,即
,故
。因此,在对数阶的时间复杂度表示方法里,我们忽略对数的 “底” ,统一表示为
。
所以,不管是以 2 为底、以 3 为底还是以 10 为底,我们可以把所有对数阶的时间复杂度都记为 。
1.2.3 线性阶 O ( N )
代码执行 N 次,其算法时间复杂度为线性阶,其最经典的例子就是数组的遍历。
for( int i = 0; i < N ; i++)
{
printf("%d",arr[ i ]);
}