一、时间复杂度的定义
在进行算法分析时,语句总的执行次数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)叫常数阶、O(n)叫线性阶、O(n²)叫平方阶,O(logn)对数阶,当然,还有其他的一些阶,我们之后会介绍。
二、大O阶推导
我们可以通过下面的方法推导出算法的时间复杂度,称之为推导大O阶:
1)用常数1取代运行时间中的所有加法常数。
2)在修改后的运行次数函数中,只保留最高阶。
3)如果最高阶项存在且不是1,则去除与这个项相乘的常数。
以上得到的结果就是大O阶。
2.1 常数阶
int sum = 0,n = 100; /* 执行一次 */
sum = (1 + n) * n / 2; /* 执行一次 */
printf("%d", sum); /* 执行一次 */
假设有如上的一个算法,没学过算法的同学可能会认为该算法的时间复杂度是O(3),但实际上是O(1)。根据我们的上面的大O阶推导,第一步,用1取代3,就成了1,第二步,该算法根本没有最高阶,所以算法时间复杂度就是O(1)。
假设有如下的算法,其中有十句 sum = (1 + n) * n / 2; /* 执行一次 */
int sum = 0,n = 100; /* 执行一次 */
sum = (1 + n) * n / 2; /* 执行一次 */
sum = (1 + n) * n / 2; /* 执行一次 */
sum = (1 + n) * n / 2; /* 执行一次 */
sum = (1 + n) * n / 2; /* 执行一次 */
sum = (1 + n) * n / 2; /* 执行一次 */
sum = (1 + n) * n / 2; /* 执行一次 */
sum = (1 + n) * n / 2; /* 执行一次 */
sum = (1 + n) * n / 2; /* 执行一次 */
sum = (1 + n) * n / 2; /* 执行一次 */
sum = (1 + n) * n / 2; /* 执行一次 */
printf("%d", sum); /* 执行一次 */
在上面的算法中,代码执行了12行,实际与最初的3行没有差别,因为与问题的本质:n的大小无关,所以最终该算法的时间复杂度仍然是O(1)。
关于上面的两种我们称之为具有O(1)的时间复杂度,又称为常数阶。
2.2 线性阶
int i;
for (i = 0; i < n; i++)
{
/* 时间复杂度为O(1)的程序步骤序列 */
}
如上面的代码所示,在循环内部的代码,其复杂度是O(1),重点在于n的大小,它决定循环的次数,则整个算法的时间复杂度是O(n)。
2.3 对数阶
int count = 1;
while (count < n)
{
count = count * 2;
/* 时间复杂度为O(1)的程序步骤序列 */
}
如上代码所示,count的增长速度是每次乘以2,假设count = 10 ,n=100,则有以下等式:
10 * 2 = 20
20 * 2 = 40
40 * 2 = 80
最终就是10 * 2的三次方 = 80。所以我们得到结论 count * 2的x次方 = n,根据大O阶推导,将count用常数1替换,则x = log2n。所以这个循环的时间复杂度是O(logn)。
2.4 平方阶
int i, j;
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
/* 时间复杂度为O(1)的程序步骤序列 */
}
}
有如上的嵌套循环,其内存循环是一个线性阶,则其时间复杂度是O(n),外部循环的时间复杂度同样是O(n),相当于内存的O(n)循环了n次,则此代码的时间复杂度就是O(n²)。
如果有如下的算法:
int i, j;
for (i = 0; i < m; i++)
{
for (j = 0; j < n; j++)
{
/* 时间复杂度为O(1)的程序步骤序列 */
}
}
上面的时间复杂度就是O(mn)。
下面看一种复杂的情况如何推导其时间复杂度:
int i, j;
for (i = 0; i < n; i++)
{
/* 注意j = i 而不是0 */
for (j = i; j < n; j++)
{
/* 时间复杂度为O(1)的程序步骤序列 */
}
}
当i = 0 时,内部执行了n次;当i = 1时,内部执行了n-1,以此类推:
n + (n-1) + (n-2) + .... .... + 1
对以上公式进行转化,首项加尾项是n+1,总共有n个n+1,则等到以下公式:
(n(n+1))/2 = n²/2 + n/2
下面根据大O阶推导:
1)没有加法常数,所以不替换
2)保留最高项,即n²/2
3)去除常数,就是去掉1/2,则等到最终的结果是n²。
如上可以得到一些结论,其实其推导过程不难,难在如何将数列进行转化,如上的等差数列等。
2.5 其他的时间复杂度
除了前面介绍的之外,还有一些其他的时间复杂度。
立方阶:O(n³)
指数阶:2ⁿ
n成对数阶:nlogn
阶乘阶:n!
n的n次方:nⁿ
这些所有的时间复杂度耗时大小如下所示:
O(1) < O(logn) < O(n) < O(nlogn) < O(n²) < O(n³) < O(2ⁿ) < O(n!) < O(nⁿ)
三、算法空间复杂度
空间换时间的方式。
算法空间复杂度的计算公式记作:S(n)=O(f(n)),其中,n为问题的规模,f(n)为语句关于n所占存储空间的函数。
我们都使用“时间复杂度”来指运行时间的需求,使用“空间复杂度”指空间需求。当不用限定词地使用“复杂度”时,通常都是指时间复杂度。
本文重点是时间复杂度,关于空间复杂度就不多做介绍了。
网友评论