阅读 286

什么是算法时间复杂度?

本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。

一、时间复杂度的定义

在进行算法分析时,语句总的执行次数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所占存储空间的函数。

我们都使用“时间复杂度”来指运行时间的需求,使用“空间复杂度”指空间需求。当不用限定词地使用“复杂度”时,通常都是指时间复杂度。

本文重点是时间复杂度,关于空间复杂度就不多做介绍了。

文章分类
后端
文章标签