算法复杂度的简单介绍
算法(Algorithm)是指用来操作数据、解决程序问题的一组方法。对于同一个问题,使用不同的算法,也许最终得到的结果是一样的,但在过程中消耗的资源和时间却会有很大的区别。因此,选择合适的评价标准去评价一个算法是很重要的。目前主要是从算法所占用的 时间和 空间两个维度去考量。
时间维度:是指执行当前算法所消耗的时间,我们通常用「时间复杂度」来描述。
空间维度:是指执行当前算法需要占用多少内存空间,我们通常用「空间复杂度」来描述。
因此,评价一个算法的效率主要是看它的时间复杂度和空间复杂度情况。然而,有的时候时间和空间却又是相互矛盾的,不可兼得,那么我们就需要从中去取一个平衡点。
下面就来分别介绍一下 时间复杂度和 空间复杂度的简单估算方式。
时间复杂度
我们如果想要知道一个算法的时间复杂度,很多人首先想到的的方法就是把这个算法程序运行一遍,那么它所消耗的时间就自然而然知道了。这种方式当然可以,不过它也有很多弊端。
这种方式非常容易受运行环境的影响,在性能高的机器上跑出来的结果与在性能低的机器上跑的结果相差会很大。而且对测试时使用的数据规模也有很大关系。再者,我们在写算法的时候,还没有办法完整的去运行呢。
因此,在估算的时候。我们一般采用另一种更为通用的符号表示方法:
T
(
n
)
=
O
(
f
(
n
)
)
T(n) = O(f(n))
T(n)=O(f(n))
在这种符号表示法中,时间复杂度的公式是:
T
(
n
)
=
O
(
f
(
n
)
)
T(n) = O( f(n) )
T(n)=O(f(n)),其中
f
(
n
)
f(n)
f(n) 表示每行代码执行次数之和,而
O
O
O表示正比例关系,这个公式的全称是:算法的渐进时间复杂度。
计算示例
先来看一段简单的代码,它的时间复杂度为 T ( n ) = O ( n ) T(n)=O(n) T(n)=O(n)
for(i=1; i<=n; ++i)
{
j = i;
j++;
}
首先,我们假设每行代码的执行时间都是一样的,以此来简化估算难度,我们用 1单位时间来表示,那么这个例子的第一行耗时是1个单位时间,第三行的执行时间是 n个单位时间,第四行的执行时间也是 n个单位时间(第二行和第五行是符号,暂时忽略),那么总时间就是 1单位时间 + n单位时间 + n单位时间 ,即
(
1
+
2
n
)
(1+2n)
(1+2n)个单位时间,即:
T
(
n
)
=
(
1
+
2
n
)
∗
单
位
时
间
T(n) = (1+2n)*单位时间
T(n)=(1+2n)∗单位时间,从这个结果可以看出,这个算法的耗时是随着n的变化而变化,因此,我们可以简化的将这个算法的时间复杂度表示为:
T
(
n
)
=
O
(
n
)
T(n) = O(n)
T(n)=O(n)
为什么可以这么去简化呢,因为这种符号表示法并不是用于来真实代表算法的执行时间的,它是用来表示代码执行时间的增长变化趋势的。
所以上面的例子中,如果n无限大的时候,
T
(
n
)
=
t
i
m
e
(
1
+
2
n
)
T(n) = time(1+2n)
T(n)=time(1+2n)中的常量1就没有意义了,倍数2也意义不大。因此直接简化为
T
(
n
)
=
O
(
n
)
T(n) = O(n)
T(n)=O(n) 就可以了。
常见的时间复杂度量级
- 常数阶 O ( 1 ) O(1) O(1)
- 对数阶 O ( l o g N ) O(logN) O(logN)
- 线性阶 O ( n ) O(n) O(n)
- 线性对数阶 O ( n l o g N ) O(nlogN) O(nlogN)
- 平方阶 O ( n ² ) O(n²) O(n²)
- 立方阶 O ( n ³ ) O(n³) O(n³)
- K次方阶 O ( n k ) O(n^k) O(nk)
- 指数阶
O
(
2
n
)
O(2^n)
O(2n)
上面从上至下依次的时间复杂度越来越大,执行的效率越来越低。
除此之外,其实还有 平均时间复杂度、均摊时间复杂度、最坏时间复杂度、最好时间复杂度 的分析方法,有点复杂,这里就不展开了。
运行效率的简单计算
将所需要处理的n代入计算得到一个数值,之后按下表参考计算就可以了。
数值 | 执行情况 |
---|---|
1000000 | 游刃有余 |
10000000 | 勉勉强强 |
100000000 | 很悬,仅限循环体非常简单的情况可以在短时间完成 |
空间复杂度
既然时间复杂度不是用来计算程序具体耗时的,那么也应该明白,空间复杂度也不是用来计算程序实际占用的空间的。
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的一个量度,同样反映的是一个趋势,我们用 S(n) 来定义。
比较常用的空间复杂度有:O(1)、O(n)、O(n²)。