谨以此文,感谢我在这个学校最喜欢的两个老师之一——肖my老师。本文基本为老师上课说讲授内容加上一部分自己的感悟拼凑而来,写作文本的目的是为自己的算法课程留下一点点东西,站在老师肩膀上形成粗糙的框架,方便以后的复习以及深入。文笔有限,其中包含的错误还请多多包容,不吝赐教。
to do list:
时间复杂度中递归树法;动规,分治新的感悟;
一:基础
部分算法、概念的前置知识
点覆盖:一组点的集合,使得图中所有边都至少与该集合中一个点相连。
支配集:一组点的集合,使得图中所有的点要么属于该集合,要么与该集合相连。
最大团:在一个无向图中找出点数最多的完全图。
独立集:一组点的集合,集合中的顶点两两不相邻。(团转过来)
SAT问题:也称布尔可满足性问题。给一组变
其中Ci被称为句子。
点覆盖<->独立集<->最大团
最小割:割是一组边集。如s-t割就是如果去掉这些边,将把原图划分为两个点集,其中一个点集包含s,一个点集包含t。(两个是指不相连,而不是代表不存在边相连,如反向边)
decision problem: 是否存在。
search problem:找到一个解。
(这个还能扩展,比如decision problem在多项式时间内解决,所以他是P问题吗)
渐进符号:
注意以上三种都是紧的,对应的两个小写的符号是不紧的,即如下图所示:
时间复杂度
概念:算法的时间复杂度是一个函数,用于定性描述算法的运行时间。注意,这个一个代表算法输入字符串长度的函数。
[注]输入字符串长度是一个比较关键的理解,比如在背包问题中,其时间复杂度为O(nW),因为W不定,所以只能是一个伪多项式时间。
比较:c < log2N < n < n * Log2N < n^2 < n^3 < 2^n < 3^n < n! < n^n
大致:常数<对数<幂函数<指数函数<阶乘
对于指数是n相关的进行比较,优先比较指数,再比较底数。
记住一个特例:n(logn)
计算:
一般来说,计算采用主方法和递归树法,其中递归树技巧性比较强,主方法其实也是递归树推导归纳而来,且主方法能得到一个比较紧的结果。
主方法:
f(n) = af(n-b)+g(n) =>O( a^(n/b) *g(n) )
P,NP,NP-Complete和NP-Hard
P:decision problems有一个多项式算法。
NP(nondeterministic polynomial-time):decision problems能够在多项式时间内验证。
NPC:NP完全问题,首先这个问题是NP的,其次,其他所有问题都可以多项式时间内归约到它。
NPH:如果所有NP问题都可以多项式时间归约到某个问题,则称该问题为NP困难。
因为NP困难问题未必可以在多项式时间内验证一个解的正确性(即不一定是NP问题),因此即使NP完全问题有多项式时间的解(P=NP),NP困难问题依然可能没有多项式时间的解。因此NP困难问题“至少与NP完全问题一样难”。
一些NP问题能在多项式时间内解决,因为 P∈NP
NP难类型问题的证明:
先选好一个已知NP难的问题,然后将已知NP难问题多项式归约到要证明的问题上。先给出这个归约,然后再证明这个归约的正确性。
NPC类型问题的证明:
证明一个问题Y是NPC问题,先说明Y是NP的,然后找到一个NPC问题X,将这个问题X归约到问题Y上,即证明完成。
常见的NPC问题(重要,规约的时候有用!):
packing problems: set-packing,独立集
覆盖问题:集合覆盖问题,顶点覆盖问题
严格满足问题(constraint satisfaction problems):SAT,3SAT
序列问题:哈密尔顿回路,旅行商问题
划分问题:3D-matching, 3着色问题
数字问题:子集合问题(子集元素之和为t),背包问题
其他:分团问题(是否存在一个规模为k的团)
规约的概念与理解
规约:意味着对问题进行转换,例如将一个未知的问题转换成我们能够解决的问题,转换的过程可能涉及到对问题的输入输出的转换。
自归约:search problem <=p decision problem
归约:A归约到B,也就是说,我们对A套一个函数f,在f函数的作用下形成一个新的问题,对这个问题运用B的黑盒解法,能够解决问题A。
(B <=p A)一般说来,B问题如果可以归约到A问题,也就是说,一个解决A问题的算法可以被用做子函数(子程序)来解决B问题,也就是说,求解B问题不会比求解A问题更困难。因此,如果B问题是困难的,那么A问题也就是困难的,因为不存在求解A问题的高效算法。(最后一句不懂)
我简单说一下我理解的规约,以X规约到Y为准,大概分成两个方面:
X规约到Y,是说X问题能够通过函数 f 转化成Y,即如果Y是一个黑盒解法,那么我对X的每一个实例通过 f 转化成Y之后,都能用这个黑盒解出来,然后再把Y的答案转回X的答案,一般来说,这个转回在定义 f 的时候就有说明。
一般来说,做这个规约是为了解决问题X,也就是说,在具体规约的时候,①应该对X的实例进行刻画,肉眼找到答案,②然后对这个实例套上 f 形成新的实例,对新的实例套用Y的黑盒,能够找到步骤一中的答案。
注:在三的一些实例中细品。
二:算法思想
贪心
概念:在对问题求解时,总是做出在当前看来是最好的选择。
贪心的证明:先假设贪心算法得到的解不是最优解,假设S1是贪心算法得到的解,而S2是所有最优解中和S1具有最多相同元素的解,然后比较S1和S2,观察S1和S2中第一个(最前面一个)不一样的元素,然后在贪心解S2中将不一样的元素换成S1中的那个元素得到另一个最优解S3,这样S3和S1比S2和S1有更多相同元素,和假设S2是与S1有最多相同元素的最优解矛盾,这样来推导S1是最优解。
我的理解:假设这个不是最优的,但是一定存在一个最优的解在某一个位置之前和我当前解结构是一样的,那么在这个位置,选最优解也可以选当前解,不影响最终答案。
[注]概念很简单,但是实际操作的时候,贪心的角度很重要,同样的贪心,方向对了,算法就是对的。
例子:
interval scheduling
给你一系列活动,每个活动有一个起始时间和一个结束时间,要求在活动不冲突的情况下找到一种有最多活动的安排。
对于这个问题,我们有一下几种贪心的角度:
①将任务按照开始时间升序排列。
②将任务按照结束时间升序排列。
③将任务按照任务时长升序排列。
④对于每一个任务,都记录与其他任务冲突的数量,按照冲突数量的升序排列。
其中1,3,4都是不可以的。
任务结束时间的贪心证明(反证法):
假设贪心不是最最优的,那我们在最优解中找一个与当前解有最相似的解。
由图可以知道,贪心贪的就是最早结束,所以如果不是最优,那么最优的结束时间一定晚于贪心的结束时间。
由上图就可以证明。
网络流
最大流通常与最小割相联系。
f 为任意一个流,cap为容量,对于任意的s-t割出来的点集(A,B),v( f ) <= cap(A, B)。
当流增加到与割的容量相等时候,就不可能再有增长空间了,称为最大流。
对于割的容量来说,不同的割法会有不同流量,有些割法永远不会有流达到,比如部分A = {s}, B = {V - s},这种把源点割出来的割法。
综上,通过这种感性的认识,如果能找到一个最小的割,那么这个割就一定是最大能跑到的流(如果流能更高的话在这个割上就会超过容量,反证。)
上图为一条增广路,一条增广路即为一条s-t的路径,在路径上仍有流可以跑,其曾广的流就是该条路径上最小的剩余容量。(相当于每找一条增广路,就至少有一条边达到满流。)
直到在图中找不到增广路,此时已经达到了最大流。
找ST集合:把满流的边去掉,从S出发走到能到的点,遍历的点就是S集合;剩下的点就属于T集合。注意,如果找到了在找S集合的时候找到了T点,说明还可以继续找增广路。
[补]有一个很有趣的延伸,如多源点多终点问题。问:如果我有两个源点s1,s2,两个终点t1,t2,我想求一组流,使得s1-t1,s2-t2的流达到最大,是否可以加一个源点S,S与s1,s2相连,边流无限大;加一个终点T,T与t1,t2相连,边流无限大,然后这组ST的最大流即可。——答案是No,无法保证是s1-t1,s2-t2,有可能交错。
动态规划和分治
例子讲的感觉不是特别好,对理解感觉起不到很大作用,希望以后有新的想法后进行补充。
三:算法的经典问题
规约
规约是一个重要的概念和思想。
独立集和点覆盖:
一个图的 最大独立集 与 最小点覆盖 是不相交的两个点集,它们的并就是整个点集。
个人理解:独立集和点覆盖都是从点的角度进行划分的,如果我们从边的角度来看,①一个最小的点覆盖即为我集合中的每一个点都尽可能与更多的边相连,②同时,一条边的两个端点中,只能有一个端点在最小点覆盖中[下注]
[注]我们假设有一条边两个端点(u,v)都在点覆盖之中,首先显然u,v都不是端点,因为假设u是端点的话只需要选择v即可;
集合覆盖 <=p 点覆盖
给一个集合S和一堆S的子集S1,S2,...,Sm,问是否存在存在k个子集,使它们的并集为S。
构造:
集合为点,集合中的元素为边,有相同元素的边相连。(注意如果某一元素只在一个子集中出现,应该怎么处理呢!)
规约:在构造的图中找最小的点覆盖,选中的点能覆盖所有的边即为对应集合的并集能包含所有的元素。所以就完成了集合覆盖到点覆盖的规约。
3SAT <= 独立集
构造:每个句子构造一个三角形,把对应变量但是相反取值的点相连。
规约:3SAT的有一个特点就是,每一个句子中至少有一个为真即可,每个句子都必须是真。将相同变量相反取值相连的目的就是,在最大独立集中,比如选择x为真,则剩下所有句子中x-ba一定不会被选中,同时由独立集和构造出来三角形的性质可以知道,每一个句子,有且仅有一个会被选中(为真)。如上图,x1-ba为真,x2-ba和x3任选一个为真即可满足。
自规约
search problem <=p decision version
比如:如果能在多项式时间内找到一个哈密尔顿圈,那么就能在多项式时间内找到一个哈密尔顿圈(删边)
在此再谈P和NP:
我们知道有些问题是可以从搜索问题规约到判断问题的,也就是所该问题如果能在多项式内判断,那么就能在多项式中搜索到,那么我们只需要说,这个判断问题能在多项式时间内求解,就叫做P问题,也就是上图红字的意思;那NP问题呢,必须要给出一个解的实例,判断的是这个实例是否满足求解问题,这个才是上图中的红字。比如,我如果能在多项式时间内判断哈密尔顿圈是否(Yes/No)存在,那这个就是ploy-time algorithm,如果我给出了一系列点,能过多项式时间内判断这些点能否构成哈密尔顿圈,那这个就是poly-time certifier。
有向哈密尔顿圈规约到无向哈密尔顿圈
构造:把一个点拆分成三个点。
3SAT规约到有向哈密尔顿圈
构造:(下面两个图要连在一起看)
从行的角度看,一行代表一个变量;从列的角度来看,每三列代表一个句子。两边中一边是两个点,一边是一个点,所以有k个句子的话,每一行有3k+3个节点。从哈密尔顿圈的答案转到3SAT的答案看这个圈在每一行是从左到右还是从右到左。
3SAT规约到子集和问题
子集和问题:给一个集合S,问是否能在集合中选取元素,使得总和为W。
构造:如下图,按照前六行和前三列进行分割,可以分成4部分,其中1,3,4部分是固定的,即在第一部分,变量v列和 变量为v(包括变量及取反)的行对应的格子为0,其余为0;第三部分全为0;第四部分按照12依次写下来。第二部分,如果Ci句子中有变量v,则记为1,因为一个句子只有三个变量,可以简单通过第二部分每一列和为3进行判定。此时集合已经构造出来,W为111444,与上面的规约相似,可以通过3SAT的简单性质进行感性的认知。
近似
近似的想法很简单,要解决一个问题,我们希望能够做到①求解结果是最优的 ②在多项式时间内解决 ③对于任意的实例都能够通过该算法解决。现在对于部分问题,无法完全满足以上要求,所以就牺牲了①,但是我们希望结果不是盲目的,所以就引入了近似的概念。
近似算法。比如2-近似,认为W为近似解,W为最优解,在求最小值的情况下W<=2W;在求最大值的情况下,W>=1/2W*
负载均衡问题
给m个机器和n个任务,每个任务有一个ti的执行时间,我们认为完成最后一个任务所需的时间为负载时间,希望能够让这个负载时间最短。
第一种:将任务依次放在机器上,当某个机器空闲时立即放入新任务。此时是2近似的。
证明:
引理1.最短时间安排是大于等于任务中时间最长的任务,L* >= max tj
引理2.最短时间安排也是大于等于所有任务的平均时间,我们在考虑放入最后一个任务前,根据我们放置的规则,该机器是耗时最短,也就是说,该机器此时的用时是低于除掉最后一个任务后的平均时长,更低于所有任务的平均时长(引理2);再根据引理1,最后一个任务应该是小于最优解的。
补充:
在这里,我还想讨论一下这个近似算法的中等于符号,先上结论:等号不一定能够找到一个实例,但是可以构造出一种结构,通过取极限求得,我们认为这样 也算是紧的。
构造实例:有m个机器,其中m(m-1)个任务的用时为1,1个任务的用时为m。肯定有一种任务集合,可以按照以下方式进行安排,此时的贪心解为19。
此时最佳的解为10,如下图:
通过推广可以知道此时的比为(2m-1)/m,当m取极限,能够达到2倍。
第二种:将任务从大到小排序,然后依次放在机器上,当某个机器空闲时立即放入新任务。此时是2近似的。
引理3:如果有大于m个任务,那么L*>=2t(m-1)。证明:t(m+1)是目前最短的任务,且目前所有机器上都有任务了,所以该任务加入时最优的情况不过是加入设备的原有任务刚好和t(m+1)相等,即等号。
中心点选取
(2近似)在n个点中,选取k个中心点,使得这些中心点能够以半径R的圆包含所有的点,让其中最大的半径最小,如下图所示:
基础:距离需要满足的三个定理①(同一性)dist(x, x) = 0 ②(自反)dist(x, y) = dist(y, x) ③(三角不等式)dist(x, y) <=dist(x, z)+dist(z, y)
r(C)为C集合中所有点的最大覆盖半径。(需要求min r(C))
算法:在点集中任选一个作为中心点,然后重复以下步骤k-1次:选取距离已选点集中最远的点,加入点集。
证明:先假设r(C)< 1/2 * r(C)以选好的点画半径为1/2 * r(C)的圆,显然可知[注],这个圆里有且仅有一个r(C)中的点。那么根据在下图中,根据三角不等式可以得出:
[注]在每个点上r(c)一定会包含到c点,而r(C)<1/2 * r(C),相当于大圆套小圆,所以c*一定在c的圆中。
带权最小点覆盖——pricing method
(2近似)问题还是很好理解的,在点上加权值,要找一个点覆盖,使得权值最小。如下图左边就是一个带权的最小点覆盖。
算法: 任选一条边(i, j)加上代价,这个代价从零开始,且这个代价的最大值低于i和j节点的权值。显然,这个边权值的最大值取决于两个端点权值的最小值,我们认为当边权值与点权值相等时,对应的那个点是紧的。把所有紧的点找出来即为点覆盖。
流程:
证明:
引理:边权之和小于等于点覆盖的点权之和。这主要是由于涉及到一条边上两个点都被选(紧的)的情况,感性认知可以看上图,缩放证明如下:
w(S)是等于所选的节点的权值之和的,等于所选节点节点所对应的边权之和,可以把它放大到所有节点对应边权之和,这样因为一条边(u, v)在u上算过一次后还要在v上算一次,所以等于边权和的两倍。再由上面引理可得。
带权最小点覆盖——线性解法
主要为了线性规划和整数规划。
(2近似)没啥好说的,只需要把方程构造出来就行了。
由于求解出来结果不一定是整数,所以我们认为某一点的值大于1/2,就选入点集。
证明:
因为xi+xj >=1,且都是正数,那必至少一个点是大于1/2的(反证,两个都小于1/2则和小于1)。
背包问题
给你n个物品和一个背包,每个物品有一个价值v和一个大小w,背包的容量是W,要求让背包装下尽可能大价值。
背包的时间复杂度:O(nW)
注意其中n表示物品的个数,无论是1个还是999个,他都是多项式的,这个很好理解。但是W就不一样了,这是一个数字。我理解的是这个数字会很奇特,比如1.00001,比如99999,这些有可能看起来不大但是实际在处理的时候很难处理的数字,统一的来说,如果我们把这些数字放在电脑上,都会以二进制的方式存储起来,有些数字用十进制表示很小,但是放在二进制上面就会很大,由W导致不能在多项式时间内解决(找不到一个范围/上界来框它)。
算法: 为了处理这个问题,我们改动了dp的状态转移方程,要让这个转移方程和W无关[注]。
此时还不是多项式的,然后我们再对value进行约。[注]
[注]这两步中,我们把w改成v,并对v进行近似处理。OPT的含义变成了,在面对是否选择第i个物品时,要想让价值达到当前值,最少的weight。理由是更改后的误差是可以忍受的:对v进行近似,结果只会出现最大价值的上下误差,如果对w进行近似,则有可能出现该物品不能放入背包中,导致整个物品直接放弃的情况。
证明: