序
算法:
是一系列解决问题的明确指令,也就是,对于符合一定规范的输入,能够在有限的时间内获得要求的输出
对于同一个问题,可以使用不同的算法,衡量不同算法之间的优劣,主要从 时间维度 和 空间维度 考虑:
时间维度: 指的是执行算法所需要的时间,通常用 “时间复杂度” 来描述
空间维度: 指的是执行算法所需要的内存,通常用 “空间复杂度” 来描述
时间复杂度
大O表示法
在描述算法的时间复杂度时,不可以直接用算法在某个电脑上解决某个问题的时间来表示,因为这与程序的语言、电脑的类型以及问题的规模等因素有关,且有些问题运行时间太久,无法得知;由此,就有了对算法的时间复杂度进行严谨分析的 大O表示法——
T
(
n
)
=
O
(
f
(
n
)
)
T(n) = O( f(n) )
T(n)=O(f(n))
其中,
n
n
n 表示数据的规模,而
O
(
f
(
n
)
)
O( f(n) )
O(f(n)) 表示运行算法所需要执行的指令的数量
常见的时间复杂度量级有(按照时间复杂度递增顺序):
- 常数阶 O ( 1 ) O(1) O(1) : 算法运行时间与数据的规模无关
- 对数阶 O ( l o g n ) O(logn) O(logn) : 算法运行时间与数据的对数成线性关系
- 线性阶 O ( n ) O(n) O(n) : 算法运行时间与数据的规模 n n n 成线性关系
- 线性对数阶 O ( n l o g n ) O(nlogn) O(nlogn)
- 平方阶 O ( n ² ) O(n²) O(n²)
- 指数阶 O ( 2 n ) O(2^n) O(2n)
- 阶乘阶 O ( n ! ) O(n!) O(n!)
递归算法的时间复杂度(Recursive algorithm time complexity)
递归函数进行一次递归调用,递归深度为 depth
在这个递归函数内部,函数的算法时间复杂度为
O
(
T
)
O(T)
O(T)
那么,总体的时间复杂度为
O
(
T
∗
d
e
p
t
h
)
O(T*depth)
O(T∗depth)
最好情况时间复杂度(Best case time complexity)
指的是特殊情况下的时间复杂度,即在最理想的情况下执行代码的时间复杂度,是该算法运行时间最短的情况
最坏情况时间复杂度(Worst case time complexity)
同样是特殊情况下的时间复杂度,即在最糟糕的情况下执行代码的时间复杂度,是该算法运行时间最长的情况
平均时间复杂度(average case time complexity)
最好情况时间复杂度和最坏情况时间复杂度都是极其特殊的情况的复杂度,发生概率很小,也不能代表平均水平
而平均情况时间复杂度是算法在所有可能的情况下,根据各种情况出现的概率加权平均后的统计时间复杂度
均摊时间复杂度(amortized time complexity)
对于一个相对耗时的操作,如果能够保证它不会每次都被触发,那么这个相对耗时的操作,其所对应的时间是可以分摊到其他的操作中的
以动态数组中的 push_back
操作为例,push_back
实现的功能是往数组的末尾增加一个元素,如果数组空间没有满,可以直接在后面插入元素;如果数组空间满了,则会将数组空间扩容一倍后,再插入该元素
例如,如果数组长度为
n
n
n,则前
n
n
n 次调用 push_back
的时间复杂度都是
O
(
1
)
O(1)
O(1) ,但是在第
n
+
1
n+1
n+1 次调用时,需要先将
n
n
n 个元素进行转移操作,之后在进行正常的插入操作,所以时间复杂度是
O
(
n
)
O(n)
O(n)
就平均而言:对于容量为
n
n
n 的动态数组,前面添加元素需要消耗
1
∗
n
1*n
1∗n 的时间,扩容操作消耗
n
n
n 时间,那么均摊时间复杂度就是
O
(
2
n
/
n
)
=
O
(
2
)
O(2n/n) = O(2)
O(2n/n)=O(2), 也就是常数阶
O
(
1
)
O(1)
O(1)
空间复杂度
算法的空间复杂度指的是运行完一个算法的对应程序,需要的内存空间的大小
程序执行时,其所需要的存储空间包括以下两个部分:
- 固定部分,包括代码空间、数据空间(常量、变量)等占用的空间,属于静态空间
- 可变空间,算法运行时需要动态分配的空间、递归栈需要的空间,是由算法决定的
算法的空间复杂度 S ( n ) = O ( f ( n ) ) S(n) = O( f(n) ) S(n)=O(f(n)), n n n 为问题的规模
总结
对于一个算法而言,其空间复杂度和时间复杂度通常是相互影响的,两者通常不可以兼得,在实际问题中,需要根据需求来选择空间复杂度和空间复杂度符号要求的算法