CS170 · 作业 4

UC Berkeley CS 170 2023年秋季 作业 4

本作业涵盖:有根树中距离至少为 K 的远方后代计数(线性 DFS 算法);基于强连通分量、DFS 先序/后序编号、DAG 最短路径的图归约;半连通有向无环图的等价刻画;2-SAT 到强连通分量的归约;以及 DFS 与 Kosaraju 算法的编码实现。

知识焦点

先建立概念地图

  • 有根树
  • DFS
  • 强连通分量
  • Kosaraju算法
  • DAG
  • 拓扑排序
  • 2-SAT
  • 半连通

建议顺序

题面 → 提示 → 答案

先完整作答,再按需展开提示;最后才展开官方参考解答并逐步核对证明与计算。

本地来源

可离线完成

题面与解答均由本地一手资料整理:hw04.pdf、hw04-sol.pdf

本地一手资料完整提取 · 作业

完整题目、提示与答案

本节按本地一手资料完整整理,共 10 道大题。本地官方解答已收录 9/10 道并默认折叠;其余题目会明确标注无本地官方解答。

使用说明

  • 所有题面均已本土化,并补足网页离线作答所需的数值、代码、表格与约束。
  • 题目与答案分层展示,展开答案前可先查看提示。
  • 本地来源:hw04.pdf、hw04-sol.pdf
1学习小组(Study Group)题目 · 提示 · 答案
官方题面翻译可离线作答无本地官方解答

完整题面

列出你的学习小组成员的姓名和 SID(学生编号)。如果你没有合作者,必须明确写出 “none”。

查看解题提示

请依据自己的实际协作或提交情况填写;本题没有唯一的标准答案。

答案状态

本地资料未提供这道题的官方解答;页面只保留题面与非答案式提示。

题面来源:hw04.pdf · 1 · confidence: high

2远方的后代(Distant Descendants)题目 · 提示 · 答案
官方题面翻译可离线作答官方解答已收录

完整题面

给定一棵有根树 \(T = (V, E)\),指定根节点为 \(r\),以及一个正整数 \(K\)。对每个顶点 \(v\),令 \(s[v]\) 表示 \(v\) 的后代中距离 \(v\) 至少为 \(K\) 的顶点个数。请描述一个 \(O(|V|)\) 的算法,对每个 \(v\) 输出 \(s[v]\)。请给出三部分解答(算法描述、正确性证明、复杂度分析)。

查看解题提示

考虑在 DFS 过程中维护当前递归栈上的一个祖先节点数组,以当前深度作为下标即可在 \(O(1)\) 时间内定位当前节点的第 \(K\) 个祖先。再思考:子节点的 \(s\) 值应如何向上合并?恰好距离为 \(K\) 的后代应在哪一步被计入,才能避免重复或遗漏?

展开官方参考解答

观察:对于顶点 \(v\),\n\[ s[v] = \sum_{c \text{ 是 } v \text{ 的子节点}} s[c] + (\text{距离 } v \text{ 恰好为 } K \text{ 的后代数量}) \tag{1} \]\n\n算法流程如下。将所有 \(s[v]\) 值初始化为 0,并从根节点开始 DFS。在算法的每一步,我们将当前节点的祖先维护在一个单独的数组中。为了确保数组中只包含当前 DFS 路径上的顶点,我们仅在真正访问某个顶点时才将其加入数组(下标等于当前深度)。由于一条路径最多包含 \(n\) 个顶点,该数组长度最多为 \(n\)。\n\n处理节点 \(v\) 时,首先定位到它在数组中的第 \(K\) 个祖先,记为 \(a\)。然后将 \(s[a]\) 加 1,以计入 \(v\) 的贡献。当处理完 \(v\) 的一个子节点 \(c\) 后,将 \(s[c]\) 加到 \(s[v]\) 上。\n\n复杂度分析:由于我们在 DFS 的每一步只执行常数个额外操作,算法仍为 \(O(|V|)\)。\n\n正确性证明:对于顶点 \(v\) 及其子节点 \(c\),每个被计入 \(s[c]\) 的节点也应被计入 \(s[v]\),因为这些节点到 \(v\) 的距离只会增加。此外,恰好距离 \(v\) 为 \(K\) 的节点不会被计入任一子节点(它们到对应子节点的距离小于 \(K\))。因此,这些节点会在访问 \(v\) 的第 \(K\) 代后代时被计入,并使 \(s[v]\) 加 1。注意,当完成对节点 \(v\) 的处理时,\(s[v]\) 的值已正确,可供其父节点使用。

解答来源:hw04-sol.pdf · 1 · confidence: high

题面来源:hw04.pdf · 1 · confidence: high

3aWhere's the Graph? (a) —— 可靠冰屋题目 · 提示 · 答案
官方题面翻译可离线作答官方解答已收录

完整题面

CS 170 课程团队正在帮助 PNPenguin 在 Antarctica 的家乡建设道路系统。由于技术所限,我们只能使用冰屋(igloo)之间的单向道路。在施工前,PNPenguin 想评估设计的可靠性:一个冰屋是可靠的,当且仅当你能沿着某条道路离开该冰屋,再沿其他道路组成的路径返回它。PNPenguin 不太擅长算法,需要你的帮助。\n\n给定我们提出的道路布局,设计一个高效算法,确定可靠冰屋的数量。

查看解题提示

提示:一个冰屋能离开后返回,意味着它位于某个有向环上。思考:图中的环对应什么结构?

展开官方参考解答

主算法思路:构造有向图 \(G = (V, E)\),顶点代表冰屋,边代表单向道路。一个可靠冰屋等价于属于某个有向环的顶点。因此我们的目标是统计 \(G\) 中属于某个环的顶点数量。\n\n首先运行 Kosaraju 算法计算 \(G\) 的所有强连通分量(SCC),并稍作修改使其同时计算每个 SCC 的顶点数量。然后,所求顶点集由所有位于大小至少为 2 的 SCC 中的顶点 \(v \in V\) 构成。\n\n正确性证明:任何 SCC \(S \subseteq G\) 都是一个子图,其中所有顶点互相可达。因此,大小至少为 2 的 SCC 中的顶点按定义构成一个环:任一顶点 \(v\) 可到达另一顶点 \(u \in S\),而 \(u\) 又能返回 \(v\)。\n\n复杂度分析:Kosaraju 算法耗时 \(O(n+m)\),遍历所有顶点确定目标集合耗时 \(O(n)\)(假设在 Kosaraju 过程中我们构建了一个以顶点为键、SCC 大小为值的哈希表)。因此总运行时间为 \(O(n+m) + O(n) \in O(n+m)\)。

解答来源:hw04-sol.pdf · 1 · confidence: high

题面来源:hw04.pdf · 1 · confidence: high

3bWhere's the Graph? (b) —— Pokemon 谱系查询题目 · 提示 · 答案
官方题面翻译可离线作答官方解答已收录

完整题面

共有 \(p\) 种不同的 Pokemon,全部源自原始的 Mew。对于任何 Pokemon 物种,Professor Juniper 都知道所有直接源自它的物种。她想编写一个程序来回答关于这些 Pokemon 的查询。\n\n程序有两个输入 \(a\) 和 \(b\),代表两种不同的 Pokemon 物种。程序以常数时间输出以下三种选项之一(时间复杂度不能依赖于 \(p\)):\n(1) \(a\) 源自 \(b\)。\n(2) \(b\) 源自 \(a\)。\n(3) \(a\) 和 \(b\) 有共同祖先,但彼此不源自对方。\n\n不幸的是,Professor Juniper 的笔记本电脑很旧,其 SSD 只能存储最多 \(O(p)\) 的数据。在上述约束下,给出 Professor Juniper 的程序可以使用的算法。\n\n提示:Professor Juniper 可以在所有查询之前对数据运行某个算法并存储其输出;此次预处理的时间不计入查询运行时间。

查看解题提示

把物种看成节点、直接衍生关系看成有向边,整棵衍生树以 Mew 为根。思考 DFS 的先序/后序编号(pre/post number)如何刻画祖先关系。

展开官方参考解答

这是一个有向图,每个节点代表一个物种,从 \(x\) 到 \(y\) 的边表示 \(y\) 直接源自 \(x\)。可将该图视为以 Mew 为根的有向树。\n\n在该图上运行 DFS,存储每个节点的先序编号(pre-number)和后序编号(post-number)。当程序收到查询时,通过检查 \(\text{pre}[b] < \text{pre}[a] < \text{post}[a]\) 判断 \(a\) 是否源自 \(b\),或通过检查 \(\text{pre}[a] < \text{pre}[b] < \text{post}[a]\) 判断 \(b\) 是否源自 \(a\),否则得出结论:\(a\) 和 \(b\) 有共同祖先但彼此不源自对方。\n\nDFS 耗时 \(O(p)\),但属于预处理。查询涉及索引两个数组并比较,耗时 \(O(1)\)。

解答来源:hw04-sol.pdf · 2 · confidence: high

题面来源:hw04.pdf · 1 · confidence: high

3cWhere's the Graph? (c) —— 盒子嵌套序列题目 · 提示 · 答案
官方题面翻译可离线作答官方解答已收录

完整题面

Bob 有 \(n\) 个不同的盒子。他想把玻璃收藏馆里著名的“蓝玫瑰独角兽”小雕像寄给暗恋对象。为保护雕像,他会把雕像放在一系列盒子里。每个盒子有重量 \(w\) 和尺寸 \(s\);由于技术进步,某些盒子具有负重量。盒子 \(a\) 放入盒子 \(b\) 时,其尺寸不能比 \(b\) 小超过 15%,否则盒子会移动,雕像会破碎。雕像必须放在 Bob 收藏中最小的盒子 \(x\) 里。\n\nBob(和 Bob 的电脑)可以问数字家庭助手 Falexa,给出所有比给定盒子 \(c\) 小不到 15% 的盒子列表(即尺寸在 \(c\) 的 0.85 到 1 倍之间的所有盒子)。Bob 需为每个单位重量支付邮费(负重量时邮局会付钱给 Bob)。找到一个算法,在关于图线性的时间内,找到可以互相嵌套的最轻盒子序列。\n\n提示:既然知道大盒子不可能放入小盒子,我们如何构造一个图?这个图具有什么性质?

查看解题提示

把每个盒子看成节点,若盒子 \(a\) 能放入 \(b\) 则连一条 \(a \to b\) 的边。由于大盒子不能放入小盒子,该图是无环的(DAG)。思考:在 DAG 上如何线性时间求最短路径?

展开官方参考解答

将每个盒子视为有向图中的一个节点,从 \(a\) 到 \(b\) 的边表示 \(a\) 能放入 \(b\)。由于 Falexa 只给出反方向的边(即告诉 Bob 哪些盒子能放入 \(b\)),我们需要先构造图再将其边反转,这可在关于图线性的时间内完成。该图是一个有向无环图(DAG):如果盒子 \(a\) 能放入 \(b\) 且盒子 \(c\) 能放入 \(a\),则 \(b\) 不可能放入 \(c\)。我们将进入节点 \(a\) 的入边权重设为对应盒子的重量 \(w\)。从最小的盒子 \(x\) 开始运行 DFS 对该 DAG 进行线性化(拓扑排序),然后按照拓扑序遍历 DAG 的顶点,通过建立在到之前节点路径基础上的方式计算每条新最短路径,从而在 \(x\) 到任意其他盒子之间找到最短路径。

解答来源:hw04-sol.pdf · 2 · confidence: high

题面来源:hw04.pdf · 2 · confidence: high

4半连通有向无环图(Semiconnected DAG)题目 · 提示 · 答案
官方题面翻译可离线作答官方解答已收录

完整题面

有向无环图 \(G\) 称为半连通的(semiconnected),如果对任意两个顶点 \(A\) 和 \(B\),存在从 \(A\) 到 \(B\) 的路径或从 \(B\) 到 \(A\) 的路径。证明:\(G\) 是半连通的,当且仅当存在一条访问 \(G\) 所有顶点的有向路径。请证明“当且仅当”的两个方向。\n\n提示:是否存在某种特定的顶点排列可以帮助我们解决这个问题?

查看解题提示

考虑拓扑序(topological ordering)。半连通性对拓扑序中相邻的两个顶点意味着什么?

展开官方参考解答

方向一:若存在一条访问所有顶点的有向路径 \(p\),则 \(G\) 是半连通的。对任意两个顶点 \(A\) 和 \(B\),考虑 \(p\) 上 \(A\) 与 \(B\) 之间的子路径。若 \(A\) 在 \(p\) 中出现在 \(B\) 之前,则该子路径从 \(A\) 指向 \(B\);否则从 \(B\) 指向 \(A\)。无论哪种情况,\(A\) 和 \(B\) 都是半连通的,这对 \(G\) 中所有顶点对 \((A,B)\) 都成立。\n\n方向二:若 \(G\) 是半连通的,则存在一条访问所有顶点的有向路径。考虑 \(G\) 的一个拓扑序 \(v_1, v_2, \ldots, v_n\)。对任意一对连续顶点 \(v_i, v_{i+1}\),由半连通性知存在从 \(v_i\) 到 \(v_{i+1}\) 的路径或从 \(v_{i+1}\) 到 \(v_i\) 的路径。但拓扑序中不存在从后向前顶点的边。因此存在从 \(v_i\) 到 \(v_{i+1}\) 的路径。该路径不能访问 \(G\) 中任何其他顶点,因为路径不能从拓扑序中靠后的顶点走向靠前的顶点。因此从 \(v_i\) 到 \(v_{i+1}\) 的路径必为单条边 \(v_i \to v_{i+1}\)。该边存在于拓扑序中任意一对连续顶点之间,故存在一条从 \(v_1\) 到 \(v_n\) 且访问所有顶点的路径。

解答来源:hw04-sol.pdf · 4 · confidence: high

题面来源:hw04.pdf · 2 · confidence: high

5a2-SAT (a) —— 矛盾强连通分量题目 · 提示 · 答案
官方题面翻译可离线作答官方解答已收录

完整题面

在 2SAT 问题中,给定一组子句,每个子句是两个文字的析取(OR)。需要为每个变量分配真值 true 或 false,使所有子句满足(每个子句至少有一个文字为 true)。本问题引导你通过将 2SAT 归约为求有向图的强连通分量来高效求解。给定一个具有 \(n\) 个变量和 \(m\) 个子句的 2SAT 实例 \(I\),构造有向图 \(G_I = (V, E)\) 如下:\n\n- \(G_I\) 有 \(2n\) 个节点:每个变量及其否定各一个。\n- \(G_I\) 有 \(2m\) 条边:对于 \(I\) 的每个子句 \((\alpha \lor \beta)\)(\(\alpha, \beta\) 为文字),\(G_I\) 中有一条边从 \(\neg\alpha\) 指向 \(\beta\),以及一条从 \(\neg\beta\) 指向 \(\alpha\)。\n\n注意,子句 \((\alpha \lor \beta)\) 等价于两个蕴含 \(\neg\alpha \Rightarrow \beta\) 和 \(\neg\beta \Rightarrow \alpha\)。在这个意义上,\(G_I\) 记录了 \(I\) 中的所有蕴含。\n\n(a) 证明:若 \(G_I\) 存在一个同时包含 \(x\) 和 \(\neg x\) 的强连通分量(对某个变量 \(x\)),则 \(I\) 没有满足赋值。

查看解题提示

若 \(x\) 与 \(\neg x\) 在同一 SCC 中,则存在从 \(x\) 到 \(\neg x\) 的蕴含链,也存在从 \(\neg x\) 回到 \(x\) 的蕴含链。这说明了什么?

展开官方参考解答

假设存在一个同时包含 \(x\) 和 \(\neg x\) 的 SCC。注意图中的边都是必要的蕴含(implication)。因此,若 \(x\) 和 \(\neg x\) 在同一分量中,则存在一条蕴含链等价于 \(x \Rightarrow \neg x\),以及另一条不同的链等价于 \(\neg x \Rightarrow x\),即子句集中存在矛盾。

解答来源:hw04-sol.pdf · 4 · confidence: high

题面来源:hw04.pdf · 2 · confidence: high

5b2-SAT (b) —— 无矛盾则可满足题目 · 提示 · 答案
官方题面翻译可离线作答官方解答已收录

完整题面

(b) 证明 (a) 的逆命题:若 \(G_I\) 没有任何强连通分量同时包含一个文字及其否定,则实例 \(I\) 一定是可满足的。\n\n提示:任取 \(G_I\) 的一个汇点 SCC(sink SCC),赋值使得该分量中的所有文字为 True。为什么我们可以这样做?为什么这不会破坏任何蕴含?

查看解题提示

考虑按逆拓扑序依次处理 SCC:每次取一个汇点 SCC,将其中文字全设为 True,然后删除它并重复。思考:为什么从不会被迫把同一变量既设为 True 又设为 False?

展开官方参考解答

任取一个汇点 SCC,并赋值使得该分量中的所有文字为 True。由于我们构造图的方式,存在一个对应的源点 SCC(source SCC),其中包含该分量中所有文字的否定。移除这一对源/汇分量,重复此过程直到图为空。由于我们以逆拓扑序将分量设为 True,因此不存在从 True 文字到 False 文字的蕴含。由于没有文字和其否定在同一 SCC 中,我们永远不会试图将一个变量同时设为 True 和 False。因此这产生了一个满足所有子句的赋值。

解答来源:hw04-sol.pdf · 4 · confidence: high

题面来源:hw04.pdf · 2 · confidence: high

5c2-SAT (c) —— 线性时间算法题目 · 提示 · 答案
官方题面翻译可离线作答官方解答已收录

完整题面

(c) 得出结论:存在一个线性时间算法求解 2SAT。请给出算法描述和复杂度分析(无需证明正确性)。

查看解题提示

结合 (a)(b) 的结论:先用线性时间 SCC 算法求出所有 SCC,再检查每个变量是否与其否定落在同一 SCC 中。

展开官方参考解答

设 \(\phi\) 是作用于 \(n\) 个文字 \(x_1, \ldots, x_n\) 上的公式。构造一个具有 \(2n\) 个顶点的图,代表所有文字及其否定。对 \(\phi\) 的每个子句 \((a \lor b)\),添加边 \(\neg a \Rightarrow b\) 和 \(\neg b \Rightarrow a\)。使用强连通分量算法,对每个 \(i\) 检查是否存在同时包含 \(x_i\) 和 \(\neg x_i\) 的 SCC。若找到任何这样的分量,报告不可满足(unsatisfiable);否则报告可满足(satisfiable)。\n\n(注意:一个常见错误是,只要图中存在从 \(x_i\) 到 \(\neg x_i\) 的路径——即使没有从 \(\neg x_i\) 回到 \(x_i\) 的路径——就报告不可满足。即使存在一系列蕴含组合得到 \(x_i \Rightarrow \neg x_i\),除非我们也知道 \(\neg x_i \Rightarrow x_i\),否则仍可将 \(x_i\) 设为 False 并可能满足所有子句。例如,考虑 2-SAT 公式 \((\neg a \lor b) \land (\neg a \lor \neg b)\):这些子句等价于 \(a \Rightarrow b\)、\(b \Rightarrow \neg a\) 等,其中 \(a \Rightarrow b \Rightarrow \neg a\) 给出 \(a \Rightarrow \neg a\),但该公式仍容易满足(令 \(a = \text{False}\) 即可)。)

解答来源:hw04-sol.pdf · 5 · confidence: high

题面来源:hw04.pdf · 3 · confidence: high

6[编码] DFS 与 Kosaraju 算法题目 · 提示 · 答案
官方题面翻译可离线作答官方解答已收录

完整题面

本周编码题中,我们将实现 DFS 并通过 Kosaraju 算法找出图中的强连通分量。你可以通过以下两种方式访问 notebook 并完成题目:\n\n1. 本地运行:从编码作业仓库 clone(或如果已 clone 则 pull) https://github.com/Berkeley-CS170/cs170-fa23-coding\n 并导航到 hw04 文件夹。参考 README.md 获取本地配置说明。\n2. Datahub:点击此处并导航到 hw04 文件夹,如果你更倾向于在 Berkeley DataHub 上完成。\n\n说明:\n- 提交说明:请下载你完成的提交 .zip 文件,提交到 Gradescope 上名为 “Homework 4 Coding Portion” 的作业。\n- OH/HWP 说明:指定的编码课程助教将在办公时间和作业派对期间提供概念和调试帮助。\n- 学术诚信指南:我们意识到要求实现的某些算法代码可能在网上容易获得,但我们强烈建议不要直接复制这些来源的代码。相反,请参考 notebook 中提到的资源并自己编写代码。我们也承认某些算法可能实现方式有限,你的解答可能与网上其他解答相似。\n\n### 图的代码表示\n课堂讲了邻接矩阵、邻接表(adjacency list)和边列表(edge list)。在 CS 170 中通常选择邻接表,因为它能高效搜索节点的邻居。多数编程问题中顶点标记为 \(0\) 到 \(n-1\),可用整数列表的列表表示邻接表。以下代码从边列表创建有向图的邻接表:\n\npython\ndef generate_adj_list(n, edge_list):\n adj_list = [[] for i in range(n)]\n for u, v in edge_list:\n adj_list[u].append(v)\n for nodes in adj_list:\n nodes.sort()\n return adj_list\n\n\n### 0.1.1 任务 1) 深度优先搜索\n\n任务 1.1 对给定邻接表表示的图执行 DFS 遍历,优先访问编号较小的顶点。返回一个长度为节点数的列表,其中第 \(i\) 个元素是探索节点 \(i\) 的节点。若节点 \(i\) 是 DFS 树的根,则第 \(i\) 个元素应为 \(i\)。确保遍历图中所有节点,即使图不连通。\n\n函数签名:def dfs(adj_list): \n返回:List[int],第 \(i\) 个元素表示调用 dfs(i) 的节点。若从某顶点开始 DFS,则 List[i] = i。 \n分值:2\n\n任务 1.2 修改你的 DFS 实现,使其能够为每个节点生成先序(preorder)和后序(postorder)编号。最小 preorder 编号应为 1,最大 postorder 编号应为 \(2 \times\)(节点数)。返回两个元组列表,每个元组为 (Node, Time Visited)。第一个列表为 preorder 访问,第二个为 postorder 访问。两个列表均按元组的第二个元素排序。\n\n> 例如,若输入是最左侧的连通分量,应返回 [(A, 1), (B, 2), (E, 4), (I, 5), (J, 6)], [(B, 3), (J, 7), (I, 8), (E, 9), (A, 10)]。\n\n可参考 DPV 笔记第 3.2.3 节图 3.6(b) 了解 pre/post order 值的样式。\n\n函数签名:def get_pre_post(adj_list): \n返回:List[Tuple[int, int]], List[Tuple[int, int]],pre 和 post order 值。 \n分值:2\n\n任务 1.3 树边、前向边、后向边、横叉边:根据 DFS 遍历得到的 DFS 森林,将边分类。后向边(back edge)\((u, v)\) 的存在表明图中存在一个包含从 \(v\) 到 \(u\) 路径上所有树边以及该后向边的环。\n\n给定图的邻接表,将边集中的每条边添加到正确的分类中。不要修改 edges_lookup 字典的初始化。\n\n函数签名:def categorize_edges(adj_list): \n返回:Dictionary({'tree': set(), 'forward': set(), 'cross': set(), 'back': set()})。 \n分值:2\n\n### 0.1.2 任务 2) 强连通分量\n\n任务 2.1 给定以邻接表表示的图,返回其反向图的邻接表(顶点集与原图相同,边集包含 \((v, u)\) 当且仅当原图边集包含 \((u, v)\))。\n\n函数签名:def reverse_graph(adj_list): \n返回:List[List[int]],反向图的邻接表。 \n分值:2\n\n任务 2.2 给定以邻接表表示的图,返回 SCC 分量的列表。每个 SCC 分量用包含该 SCC 中所有节点的集合表示。\n\n函数签名:def find_SCCs(adj_list): \n返回:List[Set[int]],每个集合包含属于对应 SCC 的所有节点。 \n分值:2\n\n### 提交\n按顺序运行 notebook 中的所有单元格,然后运行提交单元格以生成 .zip 文件用于提交。

图表条件 · DPV 笔记图 3.6(b):最左侧连通分量的有向图(节点 A, B, E, I, J),用于说明 pre/post order 编号的期望输出

有向图(最左侧连通分量):节点 A, B, E, I, J。依据期望输出 [(A,1),(B,2),(E,4),(I,5),(J,6)] / [(B,3),(J,7),(I,8),(E,9),(A,10)] 推断 DFS 树边为 A→B, A→E, E→I, I→J(B 为叶节点,在 pre=2 后立即 post=3)。

来源:hw04-sol.pdf · 3

查看解题提示

提示:Kosaraju 算法的第一步是反向图,第二步按反向图 postorder 的逆序在原图上跑 DFS 找 SCC。pre/post 编号用一个全局计数器生成。边的分类可利用 pre/post 区间和 prev 数组判断。

展开官方参考解答

任务 1.1 解答:\npython\nvisited = set()\nprev = [-1] * len(adj_list)\ndef explore(u):\n visited.add(u)\n for v in adj_list[u]:\n if v not in visited:\n prev[v] = u\n explore(v)\nfor start in range(len(adj_list)):\n if start not in visited:\n prev[start] = start\n explore(start)\nreturn prev\n\n\n任务 1.2 解答:\npython\ntime = 1\npre = []\npost = []\nvisited = set()\ndef explore(u):\n nonlocal time\n visited.add(u)\n pre.append((u, time))\n time += 1\n for v in adj_list[u]:\n if v not in visited:\n explore(v)\n post.append((u, time))\n time += 1\nfor i in range(len(adj_list)):\n if i not in visited:\n explore(i)\nreturn pre, post\n\n\n任务 1.3 解答:\npython\nprev = dfs(adj_list)\npreorder, postorder = get_pre_post(adj_list)\npre, post = {}, {}\nfor u, time in preorder:\n pre[u] = time\nfor u, time in postorder:\n post[u] = time\nfor u in range(len(adj_list)):\n for v in adj_list[u]:\n edge = (u, v)\n if pre[u] < pre[v] < post[v] < post[u]:\n if prev[v] == u:\n edges_lookup['tree'].add(edge)\n else:\n edges_lookup['forward'].add(edge)\n elif pre[v] < pre[u] < post[u] < post[v]:\n edges_lookup['back'].add(edge)\n else:\n edges_lookup['cross'].add(edge)\nreturn edges_lookup\n\n\n任务 2.1 解答:\npython\nreversed_adj_list = [[] for i in range(len(adj_list))]\nfor u in range(len(adj_list)):\n for v in adj_list[u]:\n reversed_adj_list[v].append(u)\nreturn reversed_adj_list\n\n\n任务 2.2 解答:\npython\nreversed_graph = reverse_graph(adj_list)\n_, postorder = get_pre_post(reversed_graph)\nvisited = set()\nscc_list = []\ndef explore(u):\n visited.add(u)\n curr_set.append(u)\n for v in adj_list[u]:\n if v not in visited:\n explore(v)\nfor u, _ in postorder[::-1]:\n if u not in visited:\n curr_set = []\n explore(u)\n scc_list.append(set(curr_set))\nreturn scc_list\n

解答来源:hw04-sol.pdf · 5-7 · confidence: high

题面来源:hw04.pdf · 3 · confidence: high


一手资料