评估算法的时间复杂度(time complexity)的技巧小结

本文总结了评估算法时间复杂度的技巧,包括时间复杂度概念、朴素分析方法、实际操作中的注意事项及处理更复杂情况的策略。通过实例展示了如何分析循环结构的时间复杂度,并探讨了算法在不同输入情况下时间复杂度的波动。

评估算法的时间复杂度的技巧小结

这篇文章献给澳门理工学院一起努力的同学们,祝大家早日摆脱算法学习的苦海,找到一叶扁舟。

什么是时间复杂度

众所周知,程序运行的时间长短跟硬件算法都有关系。当人们想要专注于研究算法的优劣时,就要在某种程度上排除硬件差异带来的评估干扰,这时时间复杂度的概念就被发明出来了。
时间复杂度(time complexity),是评估算法好坏的一个指标,关于它的本质,简单概括就是:时间复杂度是一个算法的输入和它运行所需的时间之间的函数特征
我们把这个输入称之为问题规模,而这个函数特征,并不是一个完全的函数,而是能够表现关系特征的一种函数类型。以下列出了最常见的函数类型,在时间复杂度中,它们表示的效率由高到低:

函数类型表示
Constant1
Logarithmiclogn
Linearn
Linearithmicnlogn
Quadraticn2
Cubicn3
Exponential2n

时间复杂度可以写作O( )形式,如O( n ), O( nlogn )

朴素方法分析时间复杂度

最朴素的分析时间复杂度的方法就是计算基础操作次数,算出运行次数关于问题规模的函数,提取出特征并得出结论
基础操作大抵包括调用,判断,赋值,读值,等等,其中读值一般特指从列表等数据结构中读取。其他在此不详细讲述了,实际操作中往往不需要使用这种分析方法。
例子:

def list_max(a,n):        # 基础操作次数
	m = a[0]					2			【赋值,读值】
	i = 1						1
	while i < n:				1*n		【n是输入的**问题规模**if a[i] > m:			2*(n-1)	【判断,读值】
			m = a[i]			2*(n−1)
			i = i +1			2*(n−1)
	return m					1
								total:7n−2

提取特征可得时间复杂度为O(n)
可以发现,所谓提取特征,就是提取原函数中最高次幂的项,去掉系数。

实际操作技巧

时间复杂度的分析主要依靠对循环(loop)的分析
首先需要明确问题规模,问题规模往往是输入的一个上限(下限),或者是输入的一组数据的大小(个数)。
一般情况下
1, 如果算法中无循环,则时间复杂度是常数O(1)。
常数时间复杂度的意思是算法的运行时间和算法的输入完全没有关联。
2,如果存在一个或多个并列的循环:
如果循环变量的上限下限与问题规模无关,如for(i = 0;i <= 5; i++),时间复杂度是O(1)
如果循环变量都以乘法递增,如for(i = 0;i <= n; i*=2),则时间复杂度是O(logn)
如果存在循环变量以加法递增,如for(i = 0;i <= n; i++),则时间复杂度是O(n)
3,如果存在嵌套的循环:
在每一个嵌套体上,根据第2条的判断方式判断出每一层循环的时间复杂度,将它们乘起来即可。如以下代码:

for(i = 0;i <= n; i*=2){
	for(j = 0;j <= n; j++){
	... ...

时间复杂度是O(nlogn)

对于一个完整的算法,按照上面的规则计算每一个代码块,根据并列相加,嵌套相乘的原则得到一个多项式,取最大次幂项特征作为时间复杂度即可。

更加复杂的情况

时间复杂度求解的过程往往会遇到更多复杂的问题,这就需要利用数学技巧计算或证明了。例如以下代码:

for(i = 0;i <= n; i*=i){
	for(j = 0;j <= i; j++){
	... ...

问题规模是n,就需要用到数列求和公式等数学技巧,或者是代码等价转换的思想来尝试求解时间复杂度了。

另一个比较复杂的问题是时间复杂度的最好情况和最坏情况,其产生的原因是:对于一个算法,除了问题规模之外,还存在其他的输入影响其运行的时间。比如,排序算法中,问题规模是排序数组的长度,但是数组内元素可能比较离散,也可能存在大量重复,这就导致排序算法排序相同长度的数组可能花费不同的时间,时间复杂度就会存在波动,这种波动往往也是一个函数
例子:Python查找链表中倒数第 i 个节点:

def back_node(self,i):
	p=self.head
	while True:
		q = p
		for j in range(i):
			if not q:
				return None
			q=q.nxt
		if not q:
			return p
		p = p.nxt

这其中,问题规模是链表长度n,但是输入的index值i也会影响算法运行的时间。
先不管i,朴素分析可得到时间复杂度原式:i∗(n−i+1) i*(n - i + 1)i(ni+1)
我们将 i 表示为:i=n/xi = n / xi=n/x带入原式中,计算得到:TimeComplexity=(x∗n2+x∗n−n2)/x2TimeComplexity = (x*n^2 + x*n - n^2) / x^2TimeComplexity=(xn2+xnn2)/x2
n固定时,当 x 取 2,也就是:i=(n+1)//2 i = (n + 1) / / 2 i=(n+1)//2所需运行时间最长。x取不同值时,函数图像大致如下:
在这里插入图片描述结论:本算法的最好时间复杂度是O(n),比如 i 取 1。最坏时间复杂度是O(n2),i 取 (n + 1) / / 2。
大多数情况下,只需要分析当问题规模固定时,其他输入影响运行时间变化的最极端情况即可。

### 最短路径算法时间复杂度对比 最短路径算法在图论中有着广泛的应用,不同的算法适用于不同类型的图以及应用场景。以下是对 Dijkstra、Bellman-Ford 和 Floyd-Warshall 三种最短路径算法时间复杂度的详细比较: #### Dijkstra 算法 Dijkstra 算法是一种用于解决单源最短路径问题的贪心算法,适用于图中所有边权值非负的情况。该算法通过优先队列(如最小堆)来选择下一个要访问的顶点,从而保证效率[^2]。 - **时间复杂度**: - 使用邻接矩阵表示图时,Dijkstra 算法时间复杂度为 $ O(V^2) $,其中 $ V $ 是图中顶点的数量。 - 使用邻接表并结合最小堆实现优先队列时,时间复杂度可以优化为 $ O(E \log V) $,其中 $ E $ 是边的数量[^1]。 ```python # 示例:Dijkstra算法时间复杂度计算 def dijkstra_time_complexity(V, E): # 使用邻接表和最小堆 time_complexity = f"O(E log V) = O({E} log {V})" return time_complexity ``` #### Bellman-Ford 算法 Bellman-Ford 算法是一种动态规划算法,用于解决单源最短路径问题,能够处理图中存在负权边的情况,但不能处理存在负权环的图。该算法通过对所有边进行 $ V-1 $ 次松弛操作来找到最短路径[^4]。 - **时间复杂度**: - Bellman-Ford 算法时间复杂度为 $ O(V \cdot E) $,其中 $ V $ 是顶点数,$ E $ 是边数。 - 在稠密图(如邻接矩阵表示的图)中,$ E $ 接近 $ V^2 $,因此时间复杂度接近 $ O(V^3) $。 ```python # 示例:Bellman-Ford算法时间复杂度计算 def bellman_ford_time_complexity(V, E): time_complexity = f"O(V * E) = O({V} * {E})" return time_complexity ``` #### Floyd-Warshall 算法 Floyd-Warshall 算法是一种用于解决多源最短路径问题的动态规划算法,能够处理有向图和负权边的情况,但不能处理存在负权环的图。该算法通过三重循环来更新任意两点之间的最短路径估计值[^3]。 - **时间复杂度**: - Floyd-Warshall 算法时间复杂度为 $ O(V^3) $,其中 $ V $ 是顶点数。 - 由于其三重循环结构,该算法在大规模图中效率较低,适用于中小型图[^5]。 ```python # 示例:Floyd-Warshall算法时间复杂度计算 def floyd_warshall_time_complexity(V): time_complexity = f"O(V^3) = O({V}^3)" return time_complexity ``` ### 总结 - **Dijkstra 算法**:适用于单源最短路径问题且边权值非负,时间复杂度为 $ O(E \log V) $,效率较高。 - **Bellman-Ford 算法**:适用于单源最短路径问题且边权值可为负,时间复杂度为 $ O(V \cdot E) $,效率较低[^4]。 - **Floyd-Warshall 算法**:适用于多源最短路径问题且边权值可为负,时间复杂度为 $ O(V^3) $,适用于中小型图。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值