「杂文」身为 ACMer 的我算法分析与设计居然挂了,我为什么会做这样的梦(雾)
写在前面
臭打 ACM 的懂个屁的算法。
记录一些会考但是 ACM 中不会在意或者概念不一样的东西。
判断
- 背包问题的最佳解决方案始终包含具有最大单位价值比vi/ci的对象i (错误)可能放不下。
- 输入规模指输入数据的个数(错误)指数据在内存中所占空间的大小,也要考虑输入数据的长度。
- 属于NP-Hard但不属于NP-Complete的问题(正确)停机问题,即给出一个程序和输入,判定它的运行是否会终止,它是不可判的(非NP)但SAT可多项式地规约到停机问题。
简答
影响时间复杂度的因素
算法执行次数、数据规模、算法基本操作、输入数据特征(是否有序)
请简述符号 \(t(n)= O(g(n))\), \(t(n)=\Omega(g(n))\),\(t(n)=\Theta(g(n))\) 的含义
-
\(t(n)= O(g(n))\) 成立的条件是:对于所有足够大的 \(n\), \(t(n)\) 的上界由 \(g(n)\) 的常数倍所确定。即存在大于 0 的常数 c 和非负的整数 \(n0\),使得对于所有的 \(n\ge n0\) 来说,\(t(n)\le cg(n)\);
-
\(t(n)=\Omega(g(n))\) 成立的条件是:对于所有足够大的 \(n\),\(t(n)\) 的下界由 \(g(n)\) 的常数倍所确定。 即存在大于 0 的常数 c 和非负的整数 \(n0\), 使得对于所有的 \(n\ge n0\) 来说, \(t(n)\ge cg(n)\);
-
\(t(n)=\Theta(g(n))\) 成立的条件是:对于所有足够大的 \(n\),\(t(n)\) 的上界和下界都由 \(g(n)\) 的常数倍所确定。即存在大于 0 的常数 \(c_1\) 和 \(C_2\) 和非负整数 \(n0\),使得对于所有 \(n\ge n_0\),\(c_2 g(n)\le t(n)\le c_1 g(n)\)。
排序
In-place和Out-place:
- in-place 占用常数内存,不占用额外内存。假如问题规模是n,在解决问题过程中,只开辟了常数量的空间,与n无关,这是原址操作,就是In-place。
- out-place 占用额外内存。开辟的辅助空间与问题规模有关,则是out-place。
稳定和不稳定:
- 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b前面。能保证排序前2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同。
- 不稳定:如果a原本在b前面,而a=b,排序之后a可能在b的后面。
Prim 和 Kruskal 的异同
相同点:都是利用了最小生成树性质的贪心。
不同点:Prim:通过扩充连通节点子集来进行贪心选择;Kruskal 通过选择具有最小权的边的集合来进行贪心选择。
贪心和 DP 的区别
相同点:都是推导算法,都具有最优子结构性质。
不同点:
-
贪心:
- 每步决策仅由上一步的最优解推导下一步的最优解,上一步以前的最优解不做保留。
- 每一步的最优解必定包含上一步的最优解。
- 自顶向下的,当前的求解不依赖于有待于做出的选择和子问题(即子树的状况),仅需选择当前局部最优解。
- 只有一个子问题。
-
动态规划:
- 全局最优解中必定包含某个局部最优解,但不必定包含前一个局部最优解,所以须要记录以前的全部最优解 (无后效性)
- 关键是状态转移方程,即如何由已求出的局部最优解来推导全局最优解。
- 自底向上的,通过子节点求出根节点,相对于贪心算法来说开销比较大。
- 有多个子问题
优缺点:
DP:优点是可以得到全局最优解;缺点是空间开销大,需要计算所有子问题
贪心:优点是思维复杂度低、代码量小、时空复杂度低;缺点是有时只能得到局部最优解。
DP 与分治的区别
相同点:都是把原问题划分为多个子问题求解。
不同点:
动态规划:
- 子问题并不是互相独立,同一个子问题可以同来求解多个问题。
- 多数自底而上来求解问题,利用额外的空间存储子问题的结果。
- 一般用于解决最优问题。
分治:
- 分子问题相互独立的,一个子问题只能求解一个问题。
- 分治一般自顶向下递归求解。
- 解决通用问题。
贪心,DP 与分治
标准分治 | 动态规划 | 贪心算法 | |
---|---|---|---|
适用类型 | 通用问题 | 优化问题 | 优化问题 |
子问题结构 | 子问题不同 | 子问题重复(不独立) | 只有一个子问题 |
最优子结构 | 不需要每个 | 必须满足 | 必须满足 |
子问题数 | 全部子问题 | 全部子问题 | 只要解决一个 |
子问题在最优解里 | 全部 | 部分 | 部分 |
选择与求解次序 | 先选择后解决子问题 | 先解决子问题后选择 | 先选择子问题后解决 |
分支界限法和回溯法的异同
- 求解目标不同:回溯法的一般是找出解空间树中满足条件的所有解。分支限界法则是尽快找出满足约束条件的一个解,或是在满足约束条件的解中找出在某种意义下的最优解。
- 搜索方式不同:回溯法深度优先,遍历结点搜索解空间树;分支限界法广度优先或最小耗费优先搜索解空间树。
- 存储空间不同:分支限界法加入了活结点表,存储空间比回溯法大得多。
- 扩展结点的方式不同:分支限界法中每个活结点只有一次机会变成扩展结点,一旦成为扩展结点便一次性生成其所有子结点。
应用:回溯法空间效率更高,分支限界法由于只需求一个解因此一般效率更高。
方法 | 回溯法 | 分支限界法 |
---|---|---|
对解空间树的搜索方式 | 深度优先搜索 | 广度优先或最小耗费优先搜索 |
存储结点的常用数据结构 | 搜索过程中动态产生问题的解空间 | 队列、优先队列 |
结点存储特性 | 活结点的所有可行子结点被遍历后才被从栈中弹出 | 每个结点只有一次成为活结点的机会 |
主要应用 | 找出满足约束条件的所有解 | 找出满足约束条件的一个解或特定意义下的最优解 |
简述 P,NP,NP-Hard,NPC 等术语。如何证明给定问题是 NP-Hard 的?
-
P:指在多项式时间内可用算法求解的问题。表示可以由确定性图灵机在多项式时间内解决的判定问题。
-
NP:指不确定是否存在多项式时间的求解算法,但可以在多项式时间内验证一个猜测解的正确性。表示可以由非确定性图灵机在多项式时间内解决的判定问题。即对于图灵机 M,如果某个字符串x在S里面,那么存在一个验证字符串u(注意这个u是针对这个x的,而且长度必须是x长度的多项式关系),M以x和u作为输入,能够验证x真的是在S里面。
-
NP-Hard:如果所有NP问题可在多项式时间内转化(归约,意思是解决了后者也就相应的解决了前者)成某个问题,则该问题称为NP难问题。如果某个问题S是NP-Hard,那么对于任意一个NP问题,我们都可以把这个NP问题在多项式时间之内转化为S,并且原问题的答案和转化后S的答案是相同的。也就是说只要我们解决了S,那么就解决了所有的NP问题。
-
NP-Complete:如果所有 NP 问题可在多项式时间内归约成某个 NP 问题,则该NP问题称为NP完全问题。也就是说 NP 完全问题既是 NP 难问题又是 NP 问题。也就是说解决了这个问题我们就解决了所有 NP 问题。
注意存在属于 NP-Hard 但不属于 NP-Complete 的问题——停机问题,即给出一个程序和输入,判定它的运行是否会终止,它是不可判的(非NP)但之后会介绍的 SAT 问题可多项式地规约到停机问题。
用来证明一个问题 L2 具有 NP-Hard 的策略如下:
- 挑选一个已知其具有 NP-Hard 的问题 L1 。
- 证明如何从 L1 的任一实例 I (在多项式确定时间内)获得 L2 的一个实例I′, 使得从 I′ 的解能(在多项式确定时间内)确定 L1 实例 I 的解。
- 从 2 得出结论 L1 ∝L2 。
- 由 1、3 及∝的传递性得出结论 L2 是 NP-Hard的。
一张经典图片,左右两侧分别为在 \(\text{P}\not= \text{NP}\) 和 \(\text{P}=\text{NP}\) 前提下四者的关系。
NP 问题是千禧年七大难题之一,即如何证明或证伪 \(\text{NP}=\text{P}\),也即是否所有能在多项式时间内验证得出正确解的问题都是具有多项式时间算法的问题,至今尚未有定论。
说到千禧年七大难题提一下千年科技学院的校徽,可以发现是 7/7 的一个图形变换:
SAT问题
SAT 是适定性(Satisfiability)问题的简称,指合取范式(Conjunctive Normal Form,CNF)的可满足问题。一个合取范式形如: \(C_1 \land C_2 \land \cdots \land C_n\),其中,子句 \(C_i (1 \leq i \leq n)\) 形如: \(l_{i1} \lor l_{i2} \lor \cdots \lor l_{ik}\)。其中,\(l_{ij}\) 称为文字,为某一布尔变量或该布尔变量的否定。例如:
SAT 问题指是否存在一组对所有布尔变量赋值 TRUE 或 FALSE 的赋值,使得整个合取范式取值为真。
对于一个 SAT 问题,若合取范式 \(F\) 中每个子句 \(C\) 中所包含的文字数目均为 \(k\),则称为 \(k\)-SAT 问题。例如对于 3-SAT 问题,可能有:
库克-列文定理:当 \(k>2\) 时,\(k\)-SAT 问题是 NPC 问题。
SAT 问题和逻辑电路是一一对应的,因此 SAT 问题可看做最难的 NPC 问题,它在建立 NP 完备性概念本身。
2-SAT 有多项式解法,见 2-SAT - OI Wiki。
计算与算法应用题
复杂度计算
见:「笔记」递归算法复杂度分析 - Luckyblock - 博客园
分支界限法
类似于回溯法,是一种在问题的解空间树 T 上搜索问题解的算法。但一般情况下与回溯法的求解目标不同。回溯法的求解目标是找出T中满足约束条件的所有解,而分支限界法的求解目标则是找出满足约束条件的一个解,或是在满足约束条件的解中找出使某一目标函数值达到极大或极小的解,即在某种意义下的最优解。
分支限界法首先生成当前扩展结点的所有分支,然后再从所有活结点中选择一个作为扩展结点。每一个活结点都要计算限界,根据限界情况判断是否剪枝,或选择最有利的结点。
分支限界法有两种不同的搜索空间树方式,分别为广度优先和最小耗费优先,它们对应两种不同的方法:
- 队列式分支限界法(FIFO):常规的广度优先策略。按照先进先出的原则选取下一个扩展结点,以队列储存活结点。
- 优先队列式分支限界法/最小耗费优先分支限界法(LC):按照优先队列中指定的优先级,选取优先级最高的结点作为下一个扩展结点,以优先队列储存。
上述两种方法分别与两种策略对应:
- 按顺序选择结点作为下一次的扩展结点。优点是节省空间,缺点是需要计算的分支数较多,时间花费大;
- 每次计算完限界后,找出限界最优的结点,作为下一次的扩展结点。优点是计算的分支数少,缺点是需要额外空间。
限界函数很大程度上决定了算法的效率。同一问题可以设计不同的限界函数。
- FIFO 分支限界法中,常以约束条件作为限界函数,满足约束条件才可入队,不满足约束条件的舍弃。
- LC 分支限界法中,还可以设计一个启发函数作为限界函数。
对于有约束的问题,FIFO法和LC法均可以求解;对于无约束问题,宜使用LC法。
实际上是一种基于剪枝的启发式搜索。
例题见下。
分支界限法背包
定义:\(W\) 表示当前所有选择物品的重量之和,\(P\) 表示当前所有选择物品的价值之和,\(BP\) 表示搜索到的所有可行解中的价值的最大值。
根节点代表扩展结点,其余每一层代表一个物品的选择情况,选中该物品则向左子树进行递归判断,否则向右子树递归。发现这是一棵二叉树,且从根节点到叶节点的 \(2^n\) 条路径与 \(2^n\) 种可能的选择方案一一对应。具体执行操作如下所示:
- 所有物品按单位价值降序排列。
- 从根节点(扩展节点)出发开始搜索;
- 对于第 \(i\) 层(代表物品 \((w_i, p_i)\))的节点,先尝试搜索左子树表示尝试选择该物品,判断是否满足约束条件(当前该物品是否可以装入背包?):
- 若可以选择则令 \(W:=W + w_i, P:=P + p_i\),继续向下搜索;
- 直至遇到不可行解时,开始向上回溯,取出最后一个装入的物品,进入右子树。
- 否则进入右子树,首先计算当前节点的上界 \(U\),即若选择进入该节点后可获得价值之和的上界。由于所有物品均为按单位价值降序排序,一种常用的上界计算为暂不考虑物品重量,令剩余容量均装入当前物品。若 \(U \le BP\) 则剪去右子树继续向上回溯;否则回到步骤 3;
- 遇到叶子节点,比较当前价值与 \(BP\),若 \(P>BP\),则 \(BP:=P\)。
- 直到遍历完所有的节点(除剪枝部分外)。
一张我自己都懒得看的图:
分支界限法求单源最短路径
参考:算法分析与设计:分支限界法_分支界限法的效率与哪些因素有关-CSDN博客
求最短的路径,考虑使用优先队列式分支限界法,以减少计算的分支数。以下图为例:
- 生成根节点的所有分支,全部入队列并记录路径;
- 在队列中选择路径最短的分支作为扩展结点
- 逐个生成分支,并判断分支的路径是否小于记录的最短路径;
- 若不小于,舍弃该分支;
- 若小于,该分支入队列;
- 生成所有分支后,回到2;
- 当队列为空时,算法结束。
分支界限搜索树:
树上所有根节点到其他节点的路径均代表一条搜索过程中访问到的路径,对于每个节点最后搜索到的从根节点到该节点的路径即为到达该节点的最短路径。
算法设计题
这个真是随便 AK。
可能需要补充的知识点
- 时间复杂度
- 贪心,DP,分治的概念、应用条件、对比
- 分支界限法搜索,回溯法搜索
- Bellman-Ford 单源最短路径
- 计算性理论(P,NP,NPC,NPH)
写在最后
参考: