一、时间复杂度与多项式时间
在计算机科学中,算法的时间复杂度是一个函数,它定性描述该算法的运行时间。这是一个代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号表述,不包括这个函数的低阶项和首项系数。使用这种方式时,时间复杂度可被称为是渐近的,亦即考察输入值大小趋近无穷时的情况。 —— 维基百科
- 常数级复杂度:不管数据有多大,程序处理所花的时间始终是那么多的,我们就说这个程序很好,具有$O(1)$的时间复杂度,也称常数级复杂度;
- 线性级复杂度:数据规模变得有多大,花的时间也跟着变得有多长,比如找n个数中的最大值这个程序的时间复杂度就是$O(n)$,为线性级复杂度
- 平方级复杂度:像冒泡排序、插入排序等,数据扩大2倍,时间变慢4倍的,时间复杂度是$O(n^2)$,为平方级复杂度。
- 指数级复杂度:还有一些穷举类的算法,所需时间长度成几何阶数上涨,这就是$O(a^n)$的指数级复杂度(EXPTIME算法)
- 甚至还存在$O(n!)$的阶乘级复杂度。
容易看出,前面的几类复杂度被分为两种级别,其中后者的复杂度无论如何都远远大于前者。像$O(1)、O(ln(n))、O(n^a)$等,我们把它叫做多项式级复杂度,因为它的规模n出现在底数的位置;
另一种像是$O(a^n)$和$O(n!)$等,它是非多项式级的复杂度,其复杂度计算机往往不能承受。当我们在解决一个问题时,我们选择的算法通常都需要是多项式级的复杂度,非多项式级的复杂度需要的时间太多,往往会超时,除非是数据规模非常小。
对于同一个问题,不同的算法可能会产生不同的复杂度。最典型的例子就是排序,对于n个整数,选择排序和冒泡排序的复杂度为$O(n^2)$,快速排序的复杂度为$O(nlogn)$,而使用基数排序的复杂度仅为$O(mn)$。
二、确定性算法
设A是求解问题B的一个解决算法,在算法的整个执行过程中,每一步都能得到一个确定的解,这样的算法就是确定性算法。
三、非确定性算法
设A是求解问题B的一个解决算法,它将问题分解成两部分,分别为猜测阶段和验证阶段,其中
- 猜测阶段:在这个阶段,对问题的一个特定的输入实例x产生一个任意字符串y,在算法的每一次运行时,y的值可能不同,因此,猜测以一种非确定的形式工作。
- 验证阶段:在这个阶段,用一个确定性算法(有限时间内)验证。
- 检查在猜测阶段产生的y是否是合适的形式,如果不是,则算法停下来并得到no;
- 如果y是合适的形式,则验证它是否是问题的解,如果是,则算法停下来并得到yes,否则算法停下来并得到no。它是验证所猜测的解的正确性。
四、判定问题Decision question
判定问题是数理逻辑中的一个重要问题。它表现为寻求一种能行的方法、一种机械的程序或者算法,从而能够对 某类问题中的任何一个 在有穷步骤内 确定其是否具有某一特定的性质。
简而言之:答案是yes或者no的问题
五、规约/约化
问题A可以约化为问题B,称为“问题A可规约为问题B”,可以理解为问题B的解一定就是问题A的解,因此解决A不会难于解决B。由此可知问题B的时间复杂度一定大于等于问题A。
《算法导论》中有一个例子:现在有两个问题:求解一个一元一次方程和求解一个一元二次方程。那么我们说,前者可以规约为后者,意即知道如何解一个一元二次方程那么一定能解出一元一次方程。我们可以写出两个程序分别对应两个问题,那么我们能找到一个“规则”,按照这个规则把解一元一次方程程序的输入数据变一下,用在解一元二次方程的程序上,两个程序总能得到一样的结果。这个规则即是:两个方程的对应项系数不变,一元二次方程的二次项系数为0。
从规约的定义中我们看到,一个问题规约为另一个问题,时间复杂度增加了,问题的应用范围也增大了。通过对某些问题的不断规约,我们能够不断寻找复杂度更高,但应用范围更广的算法来代替复杂度虽然低,但只能用于很小的一类问题的算法。存在这样一个NP问题,所有的NP问题都可以约化成它。换句话说,只要解决了这个问题,那么所有的NP问题都解决了。这种问题的存在难以置信,并且更加不可思议的是,这种问题不只一个,它有很多个,它是一类问题。这一类问题就是传说中的NPC问题,也就是NP-完全问题。
六、P问题
Polynomial-time问题,能够在 多项式时间内用算法求解的问题 。
P问题意味着计算机能够在有限时间内完成计算。整数排序问题就是P问题,不管你是采用冒泡排序还是快速排序,也无论你是对于10个整数排序还是10亿个整数排序。
七、NP问题
Nondeterministic polynomial-time问题,即 非确定性多项式时间,指不确定是否存在多项式时间的求解算法,但可以在多项式时间内验证一个猜测解的正确性的问题。
也就是说,不能判定这个问题到底有没有解,而是猜出一个解来在多项式时间内证明这个解是否正确。即该问题的猜测过程是不确定的,而对其某一个解的验证则能够在多项式时间内完成。P类问题属于NP问题,但NP类问题不一定属于P类问题。
1、N皇后问题。在学习数据结构和算法这门课时,有一个比较经典的问题叫做“八皇后问题”,它要求在一个国际象棋的棋盘上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上。这里我们把棋盘扩展到N*N,并在其中摆放N个皇后,其他要求一样。这个问题就是一个NP问题,至今为止暂时还没有多项式时间的解法。不过我们很容易验证一个解是否满足要求:对于这N个皇后中的每一个,都检查其所在行,所在列和两个斜线方向是否有其他皇后,即可验证这个解是否是正解,这种验证方法的复杂度为O(n)。
2、划分问题。以下38个整数的和为2000000:
$14175,15055,16616,17495,18072,19390,19731,22161,23320,23717,26343,28725,29127,32257,40020,41867,43155,46298,56734,$
$57176,58306,61848,65825,66042,68634,69189,72936,74287,74537,81942,82027,82623,82802,82988,90467,97042,97507,99564$
要求把它们平均分为两组,每组的19个数字之和都是1000000.
求解这个题其实并不容易,把这38个数分为两组总共有17672631900种方式。也许求出结果对于当今的计算机来说并非遥不可及,程序稍微优化一些其实并不用枚举其中的每一种情况(比如取到前17个数就已经超过1000000时,不用再取第18个和第19个数了)。然而如果是把2000个数分为两组呢?恐怕世界上最好的计算机也无能为力了。
不过,验证这个问题的解却非常容易,只需检验一下在同一组的19个数的和是不是1000000即可。
八、NPC问题(NP完全问题)
Non-deterministic Polynomial complete problem ,存在这样一个NP问题,所有的NP问题都可以约化成它。换句话说,只要解决了这个问题,那么所有的NP问题都解决了。NPC包含了NP中最难的问题。
其定义要满足2个条件:
- 它是一个NP问题;
- 所有NP问题都能规约到它。
在1971年,斯蒂芬·库克找到了第一个这样的问题:可满足性问题。简要来说,就是对于N个布尔类型的变量,它们之间采用“与”,“或”,“非”这样的逻辑运算符连接,那么这些变量能否找到一组合适的取值,使得最终的运算结果为真。与之相同的是逻辑电路问题,它其实就是可满足性问题的数字电路实现,用高电平和低电平表示真和假,用与门,或门和非门表示逻辑运算符。
伯克利的教授理查德·卡普在读过库克的论文之后,发现有一种方法可以把可满足性问题归约到团问题。团问题大致是这样一类问题:对于任意两个人,要么是微信好友,要么不是微信好友。那么能否找到一个人数为50个人的团体,使得他们两两之间彼此都是微信好友?因为库克证明了可满足性问题是NP问题中最难的问题,而卡普得到的则是,团问题不比可满足性问题要简单,至少它们一样难。卡普不仅证明了团问题是NP问题中最难的一个,而且他还找到了19个同样难度的重要问题,比如哈密顿回路,旅行商问题,最大割问题等等。1972年,他在他的论文《Reducibility Among Combinatorial Problems》中,提出了这21个NP中最难的问题。
那么这些问题叫什么名字呢?在1973年,曾经做了一次投票调查,在投票中出现了几个候选项——herculean, formidable, arduous, intractable, obstinate等等,不过最终胜出的还是”NP-Complete”(NP完全)。complete表示完全或者完备,一个事实集合被称为是完备的,表明它能解释某些逻辑系统的所有真命题。这也比较符合解决了NP完全问题,就能解决所有其他的NP问题的事实。NP完全问题也简称为NPC问题。
九、P=NP?
P=NP? 通常被认为是计算机科学最重要的问题。再回顾一下P、NP的定义:
- P是可在多项式时间内用 确定算法求解的问题。
- NP是可在多项式时间内用 不确定算法求解的问题。
前面知道,NP包含P,所以“P=NP?”问题的关键,就在于P是否也包含了NP。也就是说,如果使用确定算法,能否在多项式时间之内,解决所有不确定性算法能在多项式时间内解决的问题。
我们再来细看一下什么是多项式时间(Polynomial time)。我们都知道,$n^2$是多项式,$n^{1000000}$也是多项式。多项式与多项式之间,却有天壤之别。把解决问题所需要的时间,用“多项式”这么笼统的概念来描述,其实是非常不准确的做法。在实际的大规模应用中,$n^2$的算法都嫌慢。能找到“多项式时间”的算法,根本不能说明任何问题。对此,理论家们喜欢说,就算再大的多项式(比如 $n^{1000000}$,也不能和再小的指数函数(比如$1.0001^n$)相比。因为总是“存在”一个M,当n>M的时候,$1.0001^n$会超过$n^{1000000}$。
可是问题的关键,却不在于M的“存在”,而在于它的“大小”。如果你的输入必须达到天文数字才能让指数函数超过多项式的话,那么还不如就用指数复杂度的算法。所以,“P=NP?”这问题的错误就在于,它并没有针对我们的实际需要,而是首先假设了我们有“无穷大”的输入,有“无穷多”的时间和耐心,可以让多项式时间的算法“最终”得到优势。
十、NP难问题
Non-deterministic Polynomial hard problem(NPH)问题,如果所有NP问题都可归约成某个问题,则该问题称为NP难问题。( 满足NPC问题定义的第二条但不一定要满足第一条)
NPC问题是NP难问题的一个子集。网上看到过一幅表明P问题,NP问题,NPC问题和NP难问题的关系图,觉得非常棒,这里引用过来。
前面说过如果P=NP,那么所有的NP问题都存在有效的解决方案,而对于NP难问题来说,即使P=NP,也不一定存在有效的解决方案。
十一、求解难问题
对于一个NP问题甚至是NP难问题,通常都无法找到有效的解决方案,于是隔壁老王跟老板说:这个问题我解决不了,并且你找任何人都解决不了。老板想了想之后,把隔壁老王开除掉了,毕竟谁都解决不了,那就干脆不雇佣人好了,还能省点儿钱。
几天之后,有个叫小明的同学跟老板说:你交给隔壁老王的那个问题要求有些太高,现在没有人能解决。但是如果把要求稍微降低些,我能给你提供一个不错的解决方案。于是老板想了想觉得可以接受,于是就雇佣了小明,顶替隔壁老王之前的岗位。
在遇到几乎不可能做到的问题时,我们通常会选择退而求其次,寻找一个还能说得过去的方案。
比如我们要寻找从国贸到中关村的最短距离(当然这个问题使用穷举法也不会算太长时间,仅仅是举个栗子。如果你觉得不过瘾,可以考虑从杭州西湖到中关村的最短距离),由于中间的道路比较繁杂,穷举起来数目非常多,于是我们可以采用一种优化的方案:约定中途需要经过西直门。首先寻找从国贸到西直门的最短距离,然后寻找从西直门到中关村的最短距离。显然从国贸到中关村的最短距离,一定不会比从国贸到西直门的最短距离 加上 从西直门到中关村的最短距离要长。这么做虽然得到的可能不是最短距离的出行方案,不过却可以缩短我们的搜索范围,大大提高了解决问题的效率。当然,你还可以在国贸和西直门之间锁定另外一个地方,比如南锣鼓巷。很多地图软件就是采用这样的方案提供路线的。
上面提到的机器学习中的特征选择问题,通常采取的做法是使用前向搜索这种启发式算法:每次从所有未选择的特征中选择一个使评价结果最佳的特征,加入到备选子集中,直到加入任何特征时评价结果无法更好,或者达到了限定的特征数。这样做不能保证达到全局最优,不过却使得实现起来变得可能。