算法时间复杂度分析
1. 基本概念
-
大 O 符号 O(f(n))
表示算法的最坏情况复杂度,即算法在最不利情况下所需的基本操作数不会超过 O(f(n))的级别。例如,表示当输入规模 n 增大时,算法运行时间上界是某个常数乘以
。
-
Ω 符号 Ω(f(n))
表示算法的下界,即在任何情况下,算法至少需要做 Ω(f(n)) 数量级的基本操作。 -
Θ 符号 Θ(f(n))
当一个算法的上界和下界都为 f(n) 的量级时,我们就说该算法的时间复杂度是 Θ(f(n))。 -
平均时间复杂度
对所有可能输入的运行时间取平均得到的复杂度,反映了算法在一般情况下的表现。
2. 递归关系与主定理
在分析递归算法时,常用以下递归公式和工具:
-
递归公式的一般形式
其中 a≥1 、b> 且 f(n) 是非递归部分的工作量。
-
主定理(Master Theorem)
用于求解上述递归公式,其结果通常分为三种情况:-
情况 1: 如果
对于某个 ϵ>0,那么
-
情况 2: 如果
,则
-
情况 3: 如果
对于某个 ϵ>0 ,并且满足正则性条件,则
-
举例说明:
对于归并排序,递归公式为
这里 a=2 、b=2 ,且 f(n)=O(n) 与 同级,故满足情况2,有
3. 递归树法与迭代法
-
递归树法
将递归公式分解成一棵树,求解各层的工作量和总的层数,最终将各层的工作量累加求和。例如,对于递归关系递归树有 n 层,每层成本为 O(1) ,因此总时间复杂度为 O(n)。
-
迭代法
将递归公式展开,寻找通项后求和。例如,对于展开后可得到
4. 求和公式与级数
很多算法分析中需要利用常见的求和公式,常见的有:
-
算术级数:
-
几何级数:
当
时,总量大约为 Θ(n) 。
-
调和级数:
5. 分析方法
-
计数基本操作
通常选择算法中最耗时的操作(例如循环体内的语句、比较或交换等)进行计数,推导出操作数的数学表达式,再用大 O 表示。 -
分步分析
对于嵌套循环、递归算法或分治算法,分别分析各部分的时间复杂度,最后根据乘法或加法规则得出整体时间复杂度。 -
递归关系与主定理
对于递归算法,可以建立递归关系(例如 T(n)=2T(n/2)+O(n)),使用递归树或主定理求解出 T(n)的量级。
6. 典型算法时间复杂度举例
-
线性搜索 (Linear Search)
-
过程:在一个长度为 n 的数组中逐个比较元素,查找目标值。
-
时间复杂度:最坏情况下需要比较所有 n 个元素,故为 O(n)。
-
-
二分搜索 (Binary Search)
-
过程:在一个有序数组中,通过每次将搜索区间减半来查找目标值。
-
时间复杂度:每次操作将问题规模缩小一半,故为 O(logn)。
-
-
冒泡排序 (Bubble Sort)
-
过程:重复遍历数组,比较相邻元素并交换使得最大(或最小)元素逐步“冒泡”到末端。
-
时间复杂度:最坏情况需要进行大约
次比较,故为
。
-
-
归并排序 (Merge Sort)
-
过程:采用分治策略将数组分为若干子数组进行排序,再归并。
-
时间复杂度:分解为两个规模为 n/2 的子问题,再加上归并操作的 O(n) 时间,递归关系为 T(n)=2T(n/2)+O(n) ,解得为 O(nlogn) 。
-
-
快速排序 (Quick Sort)
-
过程:选取基准元素,将数组划分为两部分,再递归排序。
-
时间复杂度:平均情况下为 O(nlogn) ;但在最坏情况下(例如每次选取的基准都是极端值),时间复杂度为
。
-
-
Dijkstra 算法(使用邻接矩阵)
-
过程:寻找单源最短路径,采用贪心策略不断更新路径。
-
时间复杂度:对每个节点都需扫描所有节点,故为
;如果采用堆优化,则复杂度可降低为
(其中 e 边数)。
-
7. 分析实例:归并排序
我们来具体看归并排序的时间复杂度分析:
-
分解阶段:
将问题分解为两个子问题,每个子问题的规模为 n/2 ,这一步的时间复杂度为 2T(n/2) 。 -
合并阶段:
将两个已排序的子数组归并成一个有序数组需要 O(n) 时间。 -
递归关系:
T(n)=2T(n/2)+O(n).
总时间复杂度满足递归关系 -
求解递归关系:
T(n)=O(nlogn).
根据主定理,此递归关系的解为
8. 总结
-
分析方法:
对于任何算法,分析其循环、递归及分治过程,找出最耗时的基本操作,建立数学模型,再用大 O、Ω、Θ 符号描述。 -
典型例子:
-
线性搜索:O(n)
-
二分搜索:O(logn)
-
冒泡排序:
-
归并排序:O(nlogn)
-
快速排序:平均 O(nlogn)),最坏
-
Dijkstra 算法(无堆):
,(堆优化):
-