本文资料来源于机械工业出版社出版的《算法导论》。
我们已经讨论过可计算理论了,其分析哪些问题是可以被算法解决的,链接如下:yishun:可计算性理论的理解zhuanlan.zhihu.com
现在,我们来讨论算法执行的时间复杂度。
算法执行的效率主要包括2个部分:一是算法运行时所需的存储空间大小(空间复杂度),二是算法运行时所需执行的指令数(时间复杂度,决定执行时间)。本文仅讨论后者。
渐近记号
表示以下函数集合:
O(g(n))表示以下函数集合:
表示以下函数集合:
f(n)=
或 O(g(n)),表示f(n)是
或 O(g(n))的成员。
运行时间随输入规模的增长
首先要明确输入规模的概念,一般来说,它指编码算法输入所需的字节数,比如说,排序一个n个数字的数组,每个数字需要m个字节来编码,那么输入规模是m*n。但这种方法并不适用于所有情况,比如说,求大于0小于n的所有素数,输入为n,算法执行所需的时间取决于n的大小,而不是编码它所需的字节数。
输入规模的定义取决于具体问题,也取决于输入的编码方式。
“运行时间”指一个算法在特定输入上运行所需执行的指令数。有很多算法,在输入规模相同的情况下,不同的输入所需的运行时间也是不同的。比如插入排序,如果输入数组是已经排序好的话,插入排序的运行时间与输入规模是线性关系。一般来说,我们仅分析某个输入规模下,可能的最大运行时间,在某些特殊情况下,我们也会研究平均运行时间,比如快速排序。在本文中,我们仅研究算法在给定输入规模上的最大运行时间。
可以发现,算法的在给定输入规模的情况下,其最大运行时间是输入规模的函数,如下面的插入排序例子所示:插入排序代码及其分析插入排序的运行代价,令所有c均为1则得到”运行时间”
可以发现插入排序的最大运行时间是输入规模的二次函数。
在比较不同算法的执行效率时,我们比较关注的是运行时间随输入规模增长的增长量级,这是因为不同量级带来的性能差异相当巨大,占主要矛盾。
我们用第一节的O渐近符号来表示运行时间的增长量级,比如说,对于插入排序,其运行时间满足:
。我们称用O表示的运行时间增长量级为一个算法的时间复杂度,对于插入排序,其时间复杂度为
。
时间复杂度的计算
本节将讨论如何计算分治法的时间复杂度。世间算法太多,不太可能存在一个通用范式去分析所有算法,特别是对于一些随机化算法。
我们先尽可能简单的介绍一下分治法:把输入划分为若干个与原问题相同但规模更小的子问题(直到问题规模足够小,可以直接求解),递归调用自身获取子问题的解,并将其合并获取原问题的解。
令T(n)为某分治算法在输入规模为n时的的运行时间,且当n<=c时可以在常数时间
内直接求解问题。假设把原问题分解为a个子问题,且每个子问题的规模为n/b。假设分解问题时间为D(n),合并子问题的解所需时间为C(n)。那么我们可以获取T(n)的递归式如下:
求解递归式即可得到算法的时间复杂度。求解递归式的方法有以下3种:代入法:猜测一个界,带入递归式证明是否正确。(本文不做介绍)
递归树法:将递归式转化为一棵树,其节点代表不同层次递归计算的运行时间,然后采用边界和技术求解递归式。
主方法:即代公式。
首先介绍递归树法,下图为归并排序的递归式:
其递归树如下所示:
为了方便分析,上图已假设lg(n)为整数,这是采用递归树进行分析的几个重要技巧。上图还是蛮简单的,大家好好看看即可,这里不做介绍了。
下面介绍主方法:
令a
0和b>1是常数,f(n)是一个函数,有递归式T(n)=aT(n/b)+f(n),那么T(n)有如下渐近界:若对某个常数m>0,有
,则
。
若
,则
。
若对某个常数m>0,有
,且对某个常数c<1和所有足够大的n有af(n/b)
cf(n),则
。
不难发现,归并排序的递归式满足应用情况2,可以代入后直接获得渐近界。主定理的证明比较麻烦,感兴趣的同学可以直接去参考《算法导论》。
NP完全性
一般情况下,我们认为多项式时间内可解(时间复杂度为O(n^k))的问题是易于处理的,超多项式复杂度的问题是不易处理的。那么是否所有问题都可以在多项式时间内解决?答案是否定的!本节将研究一类称为“NP完全”的有趣问题,由于此类问题的分析有一定难度,所以本节的论述大多不会给出证明。
P类问题指在多项式时间内可以解决的问题,NP类问题指可以在多项式时间类证明给定“解”和输入是否匹配的问题。显然P问题都是NP问题,至于NP是否等于P,目前没有定论。
如果一个NP问题的解决难度不小于其他任何NP问题,那么我们认为此问题是NPC问题(NP完全问题)。可以发现,只要证明一个NPC问题是P问题,那么等价于证明了NP=P。
给定一个问题B,如何证明其是NPC问题呢?假设我们已知了一个NPC问题A,那么我们只要找到一个多项式算法将A转化为B即可,因为这样保证了问题B和问题A在多项式时间因子内的难度是一样的。我们称这个方法为多项式时间归约。
那么剩下的问题就是找到第一个NPC问题。这个问题是“电路可满足性问题”:给出一个由与或非组成的布和组合电路,那么这个电路是否是可满足的?即是否存在一个输入使得电路输出1。我们只需要证明每一个NP问题都可以多项式归约到这个问题即可,如下所示:
由于电路可满足性问题是一个判断性问题,即其解只有“是”或“否”,那么我们也只能把判断性问题归约到它。我们将证明可以把任何可判断性的NP问题多项式归约到电路可满足性问题,这看似没法证明后者是NPC的,实则不然,因为可判断性的问题的“表达容量”相当大,几乎任何NP问题都可以转化为对应的判断性问题。
对于任意一个判断性的NP问题L,给定输入x和“解”y(对于电路满足问题,x是电路结构,y是使电路x输出1的电路输入),必然存在算法A可以在多项式时间内判断x和y是否“匹配”。由于A是多项式时间算法,那么其执行步数和所需的内存必然都是多项式复杂度的,我们可以把算法A的执行过程展开,如下图所示:
我们可以把上图在多项式时间内转化为一个接受y作为输入的电路,如果这个电路是可满足的,即证明存在y使得原问题L的解为“是”。
综上所述,电路可满足问题是NPC问题得证。
NPC问题在很多领域都会遇到,下面将介绍几种NPC问题,其证明结构如下图所示:SAT:布尔公式的可满足性问题。
3-CNF-SAT:3合取范式的布尔公式的可满足性问题。
团问题(clique):寻找无向图中的最大团。
顶点覆盖问题(vertex cover):在无向图中找出最小规模的顶点覆盖。
哈密顿回路问题(ham-cycle):无向图是否存在哈密顿回路,即通过每个顶点的简单回路。
旅行商问题(TSP):寻找通过无向图每个顶点一次的最小回路。
子集和问题(subset-sum):给定正整数集合和正整数t,判断是否存在一个子集的元素和为t。
NPC问题远不止上面这些,如果大家有兴趣,建议参考一下其他资料,避免踩坑。如果要处理NPC问题,大致有以下3种方法:若输入规模小,这采用超多项式复杂度算法直接求解。
寻找可以在多项式时间内求解的特殊情况。
采用近似算法(approximation algorithm),在多项式时间内得到最优近似解。
本文到这里就结束了,主要围绕时间复杂度理论和NP完全性理论。