最小生成树
最小生成树是带权图的一个生成子图,它包含图中所有顶点,保持连通性,不含任何环,并且在所有满足这些条件的子图中,其边权总和最小。
ByteQuest · YouTube · 3:34
这个120秒的片段介绍了最小生成树,演示了普里姆算法在小型带权图上的执行过程,解释了为何生成树具有 V - 1 条边,并开始展示基于 heapq 的 Python 实现。该实例从顶点 A 出发,依次选取边 AB、BD、DC 和 AF,得到总权重为8。最后几秒画面从可视化图过渡到代码,但本片段内未展示完整的函数体。 这个94秒的片段演示了用 Python 实现普里姆(Prim)算法求解以邻接表形式存储的带权图的最小生成树,并分析其渐近时间与空间开销。前半部分按执行顺序高亮代码:初始化优先队列、已访问顶点集合、最小生成树边列表和总权重;标记起始顶点;将其关联边入堆;反复弹出权重最小的候选边;仅当目标顶点未被访问时才接受该边;记录该边、更新总权重,并扩展搜索边界;最后返回最小生成树的边集与总权重。后半部分切换至幻灯片,推导时间复杂度为 ``,空间复杂度为 ``。一个值得注意的细节是:旁白称“将所有连通边入堆”,而画面上的代码在入堆前已过滤掉已在 `visited` 中的邻居。
在学习检查器中查看要点和时刻,或切换阅读标签查看完整笔记。
依据视频画面与讲解整理,并非逐字语音转写。
视频片段以一个带权无向图开场,图中顶点标记为 A、B、C、D 和 F。该图全程保留在屏幕上,旁白将最小生成树定义为图的一个子集:它连接所有顶点,保持图连通,不产生环,并使所有边的总权重最小。
同一张图持续可见,此时讲解者引出普里姆算法。该方法采用贪心策略:先选定一个起始顶点,然后反复选取能扩展当前树、且不破坏连通性也不形成环的最小权重边。
演示从顶点 A 开始。动画将 A 标记为当前树的一部分,并高亮显示从 A 出发的所有边界边。其中,权重为 2 的边 AB 首先被选中并变为红色,从而将 B 加入树中。
当 A 和 B 均已加入树后,动画高亮显示当前树向外延伸的新一轮候选边。讲解者指出,满足条件的最小代价边是权重为 2 的边 BD,因此 BD 被加入,D 随之进入树中。
该过程在扩大后的树 {A, B, D} 上继续进行。当前最廉价的边界边是权重为 1 的边 DC,于是 DC 被选中,C 加入树中。
最后,在树 {A, B, D, C} 的基础上,算法选出权重为 3 的边 AF。F 加入后,所有屏幕上显示的顶点均已纳入树中,构造过程结束。所得生成树的边为 AB、BD、DC 和 AF。
非树边消失,仅留下红色的生成树。其旁侧显示文字“V - 1 条边”。旁白解释这一结构原理:若边数少于 V - 1,则必有某个顶点未被连通;若多于 V - 1,则必然出现环。
屏幕切换至 Python 代码。可见部分包含导入 `heapq`、定义函数 `prims_algorithm(graph, start)`、初始化优先队列、访问标记集合、最小生成树边列表及总代价变量;随后将起始顶点标记为已访问,并将其邻接边推入堆中。视频在此处结束,主循环仅部分显示。
片段开头展示一个 Python 函数 `prims_algorithm(graph, start)`,并立即指出运行普里姆算法所需的四个状态变量。绿色框标出 `priority_queue = []`、`visited = set()`、`mst_edges = []` 和 `total_cost = 0`。从数学角度看,它们分别对应候选边的前沿、已被纳入生成树的顶点集合、输出边集,以及待最小化的总权重值。
高亮转向初始化步骤:代码通过 `visited.add(start)` 标记选定的根顶点,随后遍历 `graph[start]`,将元组 `(weight, start, neighbor)` 推入堆中。此举确立了已访问顶点与未访问顶点之间的第一条割,并恰好将跨越该割的所有边填入前沿。旁白同时说明:`graph` 的结构假设为邻接表。
接着展示主贪心循环:当 `priority_queue` 非空时,算法执行 `weight, u, v = heapq.heappop(priority_queue)`,提取当前最廉价的候选边。条件 `if v not in visited:` 实现了普里姆方法的核心判定:仅当该边从割的已访问侧延伸至未访问顶点时,才接受它;若 `v` 已在 `visited` 中,则该边被舍弃,因其属于当前生成树内部。
当判定成功时,代码依次执行三项更新:`visited.add(v)`、`mst_edges.append((u, v))` 和 `total_cost += weight`。这三行代码正对应于数学上“每次添加一个顶点来逐步生长最小生成树”的定义,同时累加总权重。随后,算法扫描 `graph[v]`,仅将那些 `neighbor` 尚未被访问的 `(weight, v, neighbor)` 元组推入堆中,从而在割扩展后及时刷新前沿。
循环终止后,函数返回 `mst_edges, total_cost`。前者是生成树的显式边列表,后者是其所有边权值的总和。本片段未讨论连通性前提条件,但所返回的结构意在:当输入图连通时,表示一棵最小生成树。
画面切换至标题为『时间复杂度』的幻灯片。它首先显示扫描边的复杂度 ``,接着补充堆操作的复杂度 ``,最后将二者之和简化为 ``。理由是:每条边大约入队一次,而每次堆插入或提取操作在本例所用的二叉堆模型下,耗时为顶点数的对数级。
下方新增第二标题『空间复杂度』。幻灯片依次累加邻接表图的存储开销 ``、已访问顶点集合的开销 ``、优先队列的开销 ``,以及最小生成树边列表的开销 ``,最终合并为 ``。这明确指出:内存消耗主要由图本身的存储及若干线性规模的辅助结构主导;而运行时间则主要由边上的对数级堆操作主导。
最小生成树是带权图的一个生成子图,它包含图中所有顶点,保持连通性,不含任何环,并且在所有满足这些条件的子图中,其边权总和最小。
Prim算法从一个选定的起始顶点出发,通过反复添加一条权重最小的边,将当前生成树与树外某个顶点连接起来,从而逐步构建最小生成树。该过程在每一步都保持连通性,且避免产生环。
优先队列用于存储候选的边界边,使算法能在每次迭代中快速提取出权重最小的边。在Python示例代码中,`heapq` 模块提供了这种最小堆行为。
从图中顶点A出发,所选边为AB(2)、BD(2)、DC(1)和AF(3)。这四条边连接了图中所有顶点,且不构成环,其总权重为8。
任意含V个顶点的生成树恰好有V - 1条边。若边数更少,则无法连通所有顶点;若边数更多,则必然包含环。
可见代码定义了函数 `prims_algorithm(graph, start)`,初始化了 `priority_queue`、`visited`、`mst_edges` 和 `total_cost`;将起始顶点 `start` 标记为已访问;将从 `start` 出发的各条边以 `(weight, start, neighbor)` 元组形式压入队列;随后开始循环弹出权重最小的候选边,并检查其目标顶点是否尚未访问。
该实现起始于创建四个承担不同职责的容器:`priority_queue` 按权重排序保存候选边;`visited` 记录已加入生成树的顶点;`mst_edges` 存储已被采纳的边;`total_cost` 累计目标值。它们共同实现了前沿管理、成员判定、输出构建与代价核算的职责分离。
Prim 算法首先选定一个根顶点,将其标记为已访问,并将所有与其关联的边插入优先队列。代码中,`visited.add(start)` 后紧接一个遍历 `graph[start]` 的循环,逐个压入元组 `(weight, start, neighbor)`。此举在生成树与图其余部分之间建立起初始割。片段说明 `graph` 为邻接表。
算法的核心是一个循环:反复从堆中弹出权重最小的边,并检查其终点是否为新顶点。代码使用 `weight, u, v = heapq.heappop(priority_queue)` 提取边,再以 `if v not in visited:` 判定。这实现了贪心准则——始终考虑跨越当前割的最廉价边。
若弹出的边抵达一个未访问顶点,则算法将该顶点正式纳入生成树,记录该边,将其权重加至总代价,并插入由此顶点出发的新相关边。可见代码更新了 `visited`、`mst_edges` 和 `total_cost`,随后遍历 `graph[v]`,并在满足 `if neighbor not in visited:` 条件时才执行压入操作。需注意:旁白称所有相连边均被压入,但实际展示的代码会过滤掉已访问的邻居。
当优先队列为空时,函数输出所收集的边列表及累计权重。`mst_edges` 描述生成树的结构,而 `total_cost` 给出边权重之和的最小值。本片段将此呈现为该算法的最终结果。
幻灯片将运行时间分解为边扫描与堆操作两部分。遍历所有边贡献 ``,而每条边大约入堆一次,每次堆操作耗时对数级,因此堆操作共贡献 ``。由于对数项占主导地位,总时间复杂度为 ``。
内存使用按数据结构逐项分析:邻接表表示的图需 ``,已访问集合需 ``,优先队列最多容纳 `` 条边,最小生成树边列表需 ``。各项相加并化简后,总空间复杂度为 ``。
按知识点查看条件、步骤和证据。补充解释与视频直接内容分别标明。
讲述者指出,在任意一棵最小生成树中,边的数量总是恰好等于顶点数减一,其中顶点数用 V 表示。
屏幕文字在最终的红色生成树旁显示“Edges”,其上方为“V - 1”。
V
图中顶点的个数。
正整数;在本例演算中,。
讲解员指出,任意最小生成树恰好有 V - 1 条边。
屏幕上显示的公式为“V - 1 Edges”。
V - 1
含 V 个顶点的生成树中边的条数。
Python 代码显示 `def prims_algorithm(graph, start):`,随后使用了 `graph[start]`。
讲解员说明该函数以图和起始节点作为输入参数。
可见部分未完整指定邻接表数据结构的具体形式。
graph
Prim 算法实现所采用的输入图表示方式。
Python 代码显示 `def prims_algorithm(graph, start):`,接着是 `visited.add(start)` 和 `for neighbor, weight in graph[start]:`。
讲解者指出,该函数将起始节点作为输入参数。
start
普里姆算法构建生成树时所选取的初始顶点。
Python 代码初始化 `priority_queue = []`,并使用 `heapq.heappush(priority_queue, (weight, start, neighbor))` 和 `heapq.heappop(priority_queue)`。
此前的讲解提到,优先队列可存储边,以便快速获取权重最小的边。
priority_queue
以权重为主要排序依据、存储候选边元组的最小堆。
Python 代码初始化 `visited = set()`,随后调用 `visited.add(start)` 并检查 `if v not in visited:`。
visited
已加入不断生长的生成树中的顶点集合。
Python 代码初始化 `mst_edges = []`。
本片段中未见向 `mst_edges` 追加元素的操作。
mst_edges
用于收集所选最小生成树边的列表。
Python 代码初始化 `total_cost = 0`。
本片段中未见对 `total_cost` 的更新操作。
total_cost
用于累加所选生成树边总权重的累加器。
Python 代码包含 `for neighbor, weight in graph[start]:`,并压入元组 `(weight, start, neighbor)`。
neighbor, weight
从起始顶点的邻接表中读取的相邻顶点及其对应边的权重。
Python 代码包含 `weight, u, v = heapq.heappop(priority_queue)`。
weight, u, v
弹出的候选边元组:边的权重、源顶点、目标顶点。
所显示的代码将 `graph` 用作 `prims_algorithm(graph, start)` 的第一个参数,并以 `graph[start]` 和 `graph[v]` 的方式对其索引。
在00:24-00:27处,讲解者说:“这假设图以邻接表形式存储。”
graph
以邻接表形式表示的带权图;每个条目将一个节点映射到其带权邻边的集合。
Prim 算法所使用的带权图数据结构。
代码中定义了 `def prims_algorithm(graph, start):`,随后出现 `visited.add(start)` 以及 `for neighbor, weight in graph[start]:`。
在00:17-00:23处,讲解者说:“接着,我们将起始节点加入已访问集合,并将其邻边及其权重一并推入优先队列。”
start
Prim 算法开始构建最小生成树时所选取的初始顶点。
输入图中的一个顶点。
讲解员将最小生成树定义为图的一个子集,该子集以尽可能小的总边权重连接所有顶点,保持连通性,且不含环。
在讲解定义的同时,展示一个带权无向图,其顶点为 A、B、C、D、F,并含多条边。
最小生成树是带权图的一个生成子图,它包含图中所有顶点,保持连通,不含环,且使其所有边的权重之和最小。
适用于带权图。
所选子图必须连接所有顶点。
所选子图必须是无环的。
目标是使所有边的总权重最小。
讲解者表示将使用普里姆(Prim)算法求最小生成树:首先选定一个顶点,然后逐步添加权重最小的边,在保持图连通且无环的前提下扩展生成树。
普里姆算法从一个选定的起始顶点出发,通过反复添加能扩展当前树、且不会形成环的最廉价边,来构建一棵生成树。
要求输入图为连通的带权图,以保证能覆盖所有顶点生成一棵生成树。
从一个选定的顶点开始。
每条被采纳的边都必须将当前树连接至一个新顶点。
讲解者指出,可用优先队列来存储边,从而快速检索出权重最小的边。
在普里姆算法中,优先队列用于存储候选边,以便高效地提取下一条最小权重的边。
候选边按权重排序。
该队列支持对最小权重项的快速提取。
讲解员指出,任意最小生成树恰好有1条边,其中V表示顶点数量。
文字“V - 1 条边”出现在最终生成树旁。
对于含有V个顶点的图,任意生成树(包括最小生成树)都恰好包含V - 1条边。
该图含有V个顶点。
该子图是一棵生成树,即连通且无环。
可见的Python代码导入`heapq`,定义函数`prims_algorithm(graph, start)`,初始化`priority_queue`、`visited`、`mst_edges`和`total_cost`,将起始节点标记为已访问,将起始节点的邻接边压入堆,并开始从堆中弹出元素,同时进行`v not in visited`检查。
讲解员介绍Python代码,提到导入`heapq`以实现优先队列,并定义接收图和起始节点作为输入的函数。
视频片段在完整循环体和返回语句显示之前结束。
可见代码用候选边的最小堆、记录生成树顶点的已访问集合,以及用于收集最小生成树边和总代价的占位符,实现了Prim算法。
使用`heapq`作为最小堆优先队列。
图用邻接表表示,因此 `graph[start]` 返回形如 `(邻居节点, 边权)` 的二元组。
已加入生成树的顶点记录在 `visited` 中。
屏幕上显示 `priority_queue = []`、`visited = set()`、`mst_edges = []` 以及 `total_cost = 0`,位于 `def prims_algorithm(graph, start):` 函数内部。
讲解者按从00:02到00:17的顺序,依次说明每个变量的作用。
该实现首先创建四个状态容器:一个用于候选边的优先队列、一个用于已加入生成树的顶点的 visited 集合、一个用于存储最小生成树边的列表,以及一个用于累计总代价的数值累加器。这些对象分别对应算法的前沿、成员判定、输出结果和目标值。
该图是带权图。
该实现使用最小堆优先队列。
假设图以邻接表形式存储。
高亮显示的代码为 `visited.add(start)`,随后是 `for neighbor, weight in graph[start]: heapq.heappush(priority_queue, (weight, start, neighbor))`。
在00:17-00:23处,讲解者指出起始节点被加入 `visited`,且其所有邻接边被压入优先队列。
在00:24-00:27处,讲解者说明图以邻接表形式存储。
普里姆算法首先将一个顶点标记为已在生成树中,并将其所有关联边插入优先队列。这就在已访问顶点与未访问顶点之间形成了初始割集,使得后续步骤可反复选择最廉价的横跨边。
选定一个起始顶点 `start`。
`graph[start]` 枚举所有邻接的 `(neighbor, weight)` 对。
边以 `(weight, start, neighbor)` 形式压入,使堆按权重优先排序。
代码显示 `while priority_queue:` 和 `weight, u, v = heapq.heappop(priority_queue)`,随后是 `if v not in visited:`。
在00:28-00:35处,讲解者说:“只要优先队列非空,我们就弹出权重最小的元素,并检查该节点是否已在已访问集合中。”
核心贪心步骤反复从优先队列中移除权重最小的边,并检验其目标顶点是否仍在生成树之外。若目标顶点已被访问,则丢弃该边;否则,该边成为下一条被接受的最小生成树边。
堆按权重顺序存储候选边。
在此实现中,仅检查目标顶点 `v` 是否在 `visited` 中。
高亮代码块为:`visited.add(v)`、`mst_edges.append((u, v))`、`total_cost += weight`,然后执行 `for neighbor, weight in graph[v]: if neighbor not in visited: heapq.heappush(priority_queue, (weight, v, neighbor))`。
在00:36-00:47处,讲解者说:将节点加入 `visited`,将该边加入最小生成树,更新总代价,并将所有相连的边压入优先队列。
讲解词说“当前节点连接的所有边”,而屏幕上可见的代码仅压入那些邻居尚未被访问的边。
当弹出的边到达一个未访问顶点时,算法将该顶点正式纳入生成树,记录该边,将其权重加至总代价,并将该顶点新可到达的所有出边插入优先队列。此举维持了堆中始终包含从已访问集合指向图其余部分的候选边这一不变性。
弹出的边 `(u, v)` 满足 `v not in visited`。
`graph[v]` 提供相邻的 `(neighbor, weight)` 对。
实现过程会在将邻居节点加入优先队列前,先过滤掉已在 `visited` 中的邻居。
屏幕上显示的最后一行是 `return mst_edges, total_cost`。
在00:47-00:52处,讲解者说:“最后,我们返回最小生成树的边集及其总代价。”
当优先队列耗尽后,函数同时输出所选边的列表与累积权重。该边列表描述了树的结构,而标量值则给出了优化目标的取值。
主循环已终止,原因是 `priority_queue` 为空。
图的连通性足以保证遍历生成一棵生成树;但这一条件在视频片段中并未明确说明。
幻灯片首先显示 `TIME COMPLEXITY` 及 ``,接着显示 ``,最后简化为 ``。
在00:57-01:10处,讲解者指出:遍历所有边的时间复杂度为 ``,每条边至多被压入队列一次,每次压入耗时对数级,因此总时间复杂度为 ``。
算法在线性时间内扫描邻接信息(以边数为基准),每次堆插入或删除操作耗时对数级。由于每条边大约仅进入优先队列一次,主导项为堆操作开销,故总体时间复杂度为 ``。
优先队列以二叉堆方式实现。
每条边最多被插入约一次。
`V` 表示顶点数量,`E` 表示边的数量。
幻灯片显示 `SPACE COMPLEXITY`(空间复杂度),并先构建出 ``,再化简为 ``。
从 01:11 到 01:33,讲解者将邻接表表示的图的空间复杂度记为 ``,已访问顶点集合记为 ``,优先队列记为 ``,最小生成树边列表记为 ``,进而得出总空间复杂度为 ``。
内存使用主要由图本身的存储决定,此外还需额外结构来记录已访问顶点、前沿堆以及输出边列表。将这些项相加并化简后,得到总空间复杂度为 ``。
图以邻接表形式存储。
已访问集合最多可包含全部顶点。
优先队列最多可包含全部边。
最小生成树边列表最多存储 `V - 1` 条边,其上界为 ``。
讲解者明确陈述了最小生成树的定义性质。
最小生成树是图的一个子集,它连接所有顶点,总边权最小,保持连通性,且不含环。
基础图是带权图。
所选子图覆盖所有顶点。
对于给定的带权图,在所有生成树中,最小生成树取得最小的总边权。
讲解者描述了从一个顶点出发,逐步添加权重最小的边,同时确保连通性与无环性。
普里姆算法通过反复添加能将当前连通无环子树扩展至新顶点的最小权重边,来构造最小生成树。
该图是连通的且带权。
已选定一个起始顶点。
当前选中的边构成一棵树。
在每次迭代中,选择一条权重最小的可行边。
讲解员指出:任意一棵最小生成树恰好有 V - 1 条边。
屏幕文字显示“V - 1 条边”。
对于一个含有 V 个顶点的图,其任意一棵最小生成树都恰好包含 V - 1 条边。
该子图是 V 个顶点上的一棵生成树。
对于任意一棵 V 个顶点上的生成树,其边数都等于 V - 1。
讲解员解释:如果边数更少,该图将不连通。
若一个 V 个顶点上的生成子图所含边数少于 V - 1,则它不连通。
该子图旨在包含全部 V 个顶点。
对于所有含 V 个顶点且边数少于 V - 1 的子图。
讲解员说明:若边数更多,便会形成环,而树中不允许出现环。
若一个含 V 个顶点的连通子图具有多于 V - 1 条边,则它包含一个环,因此不是一棵树。
该子图包含全部 V 个顶点且是连通的。
对于所有含 V 个顶点且边数多于 V - 1 的连通子图。
在 00:24-00:27 处,讲解者说:“这假设图以邻接表形式存储。”
代码将 `graph[start]` 和 `graph[v]` 视为可迭代的邻居列表并进行索引。
所展示的普里姆算法实现假设输入图以邻接表形式存储。
该函数接收一个名为 `graph` 的参数。
`graph[node]` 可被遍历,从而生成 `(neighbor, weight)` 对。
针对本片段中展示的实现。
在‘时间复杂度’标题下,幻灯片简化为 ``。
在01:00-01:10处,讲解者得出总时间复杂度为‘E log V 的大 O 表示法’。
对于本片段中展示的基于堆的实现,总时间复杂度为 ``。
该图具有 `V` 个顶点和 `E` 条边。
优先队列是一个二叉堆。
每条边大约被压入一次,且堆操作耗时为对数级。
所呈现算法的渐近最坏情况界。
在“空间复杂度”标题下,该幻灯片简化为 ``。
在01:30-01:33处,讲解者说:“因此,总空间复杂度为 V 加 E 的大 O 记号。”
对于所展示的实现,总空间复杂度为 ``。
图以邻接表形式存储,需占用 `` 空间。
已访问集合占用 `` 空间。
优先队列占用 `` 空间。
最小生成树边列表占用 `` 空间。
所呈现算法的渐近内存界。
讲解员依次演示选择 A、AB、BD、DC、AF,并指出所有节点均已连通,因此算法终止。
图中以绿色高亮候选边,以红色标示已采纳的边,同时顶点按顺序变为黄色。
选择顶点 A 作为起始顶点。
讲解员说:“我们从节点 A 开始。”
在与顶点 A 关联的所有边中,选择权重为2的边 AB,并将其加入生成树。
AB 是当前生成树 {A} 中通往外部顶点的最小权重边;动画将 AB 变为红色,B 变为黄色。
从当前生成树 {A, B} 出发,考察所有跨越到外部顶点的边,并选择权重为2的边 BD。
讲解员指出,高亮显示的最小代价边是权重为2的 BD;动画将 BD 以红色加入,并将 D 标记为黄色。
从当前生成树 {A, B, D} 出发,选择权重为1的边 DC 并将其加入。
DC 是当前可选的最小权重跨割边;动画将 DC 染为红色,并将 C 标记为黄色。
从当前生成树 {A, B, D, C} 出发,选择权重为3的边 AF 并将其加入。
AF 是到达顶点 F 所需的剩余最小权重边;动画将 AF 染为红色,并将 F 标记为黄色。
停止,因为所有顶点均已包含在内。
讲解员指出:通过此次添加,所有节点均已连通,算法终止。
所得的最小生成树由边 AB、BD、DC 和 AF 构成,总权重为。
讲解员给出直观解释:边数过少会使图不连通,边数过多则会产生环。
这是一种直观解释,而非严格证明。
一个包含 V 个顶点的生成树恰好有 V - 1 条边。
由讲述者直接陈述,并在屏幕上展示。
如果边数少于 V - 1 条,该图将不连通。
讲述者所陈述的关于边数不足、无法连接所有顶点的直观理解。
如果边数多于 V - 1 条,则会形成环。
讲述者所陈述的关于多余边违反树结构条件的直观理解。
精确的边数 V - 1 是由“覆盖所有顶点”与“保持无环”这两个要求共同决定的。
图示演算过程为 `` → `` → ``。
讲解者解释了边的遍历、近似每条边仅入队一次、每次入队代价为对数级,以及最终的简化过程。
遍历所有边带来与边数成线性关系的时间开销。
叙述者在00:57-01:00处直接陈述,并在幻灯片中展示。
每条边最多被压入优先队列一次,每次压入操作耗时对数级。
叙述者在01:00-01:07处直接陈述,并体现在第二张幻灯片的对应项中。
该求和式通过保留主导的渐近项得以简化。
在幻灯片中以可视化方式呈现,并在01:07-01:10处由叙述者口头得出结论。
所介绍的基于堆的Prim算法的总运行时间为``。
可视化序列是`` → `` → `` → `` → ``。
叙述者将各项分别对应到图、已访问集合、优先队列和最小生成树边列表,然后求和。
图本身占用``空间,因其以邻接表形式存储。
由讲解员在01:11-01:15处陈述,并作为幻灯片上首项显示。
访问集合占用 空间,因为它最多存储全部顶点。
由讲解员在01:16-01:20处陈述,并作为第二项追加。
优先队列占用 空间,因为在执行过程中它可能容纳全部边。
由讲解员在01:21-01:26处陈述,并作为第三项追加。
最小生成树边列表占用 空间。
由讲解员在01:26-01:30处陈述,并作为第四项追加。
将辅助空间界相加并化简,得到总内存需求。
在幻灯片上以可视化方式呈现,并于01:30-01:33处通过口头总结得出。
所展示实现的总空间使用量为 。
带权图,顶点为 A、B、C、D、F,可见的边权为:AB=2,AF=3,AD=5,BD=2,BC=7,BF=4,CD=1。
讲述者说:“现在,我们用普里姆算法来求解这个示例图的最小生成树。”
被选中的边按 AB、BD、DC、AF 的顺序变为红色。
图中未显示顶点E,尽管标签集为五个字母:A、B、C、D、F。
使用普里姆算法,从顶点A出发,求出所展示的带权图的最小生成树。
已显示的顶点:A、B、C、D、F。
已显示的边权:AB=2,AF=3,AD=5,BD=2,BC=7,BF=4,CD=1。
起始顶点:A。
选取一个连通、无环且覆盖所有顶点的边子集,使其总权重最小。
从仅含顶点A的单顶点树开始。
叙述中明确指出了起始选择。
添加边AB,因为它是从A出发的最便宜的边。
当前树上关联边中的最小权重边。
从当前树{A,B}出发,添加边BD。
这是旁白提到的当前树中成本最低的容许横跨边。
将边 DC 加入当前树 {A,B,D}。
这是逐步演示中接下来选出的最小权重边。
添加边 AF 以包含最后一个顶点 F。
旁白选择 AF 作为下一条最小权重边,随后停止。
将所选边的权重相加,得到整棵树的总权重。
由所选边的可见权重推导得出。
最小生成树为 {AB, BD, DC, AF},总权重为8。
所选边连接了所有显示的顶点 A、B、C、D、F,包含条边(对应显示的顶点集),且不构成环。
展示了一个静态的带权无向图:顶点为青绿色圆形,标有字母 A、B、C、D、F;边为深蓝色,标有数字权重。
顶点 A、B、C、D、F
边的权重分别为1、2、2、3、4、5、7
图在定义与算法概述讲解过程中被引入并保持不变。
顶点标签保持固定。
边的权重保持固定。
该图示确立了将用于演示普里姆算法的具体实例。
顶点A最先变为黄色;当前生成树出发的候选边亮起绿色;已被采纳的边变为红色;新加入的顶点依次变为黄色:B、D、C、F。
每次颜色变化的精确逐帧时间仅为近似值。
当前生成树中的顶点以黄色高亮显示
候选的横跨边以绿色高亮显示
已被采纳的生成树边以红色高亮显示
从顶点A开始。
高亮从顶点 A 出发的边。
选择边 AB。
从集合 {A,B} 扩展候选集。
选择边 BD。
从集合 {A,B,D} 扩展。
选择边 DC。
从集合 {A,B,D,C} 扩展。
选择边 AF。
所有顶点均成为生成树的一部分。
原始图的拓扑结构与边权值均不发生变化。
仅通过颜色编码的变化来标识顶点是否属于生成树以及是否为候选顶点。
该动画展示了普里姆算法的贪心规则:始终维持一棵连通的树,并反复添加代价最小的边界边。
非树边消失或淡出,仅保留红色树边 AB、BD、DC、AF;右侧显示文字“V - 1 条边”。
红色生成树边 AB、BD、DC、AF
黄色顶点 A、B、C、D、F
文字“V - 1 条边”
画面从完整图切换为孤立的生成树。
公式文字出现在生成树旁。
所选生成树保持连通且无环。
顶点集合保持不变。
视觉效果强调输出结果是一棵生成树,且其边数满足 V - 1 条边这一规则。
屏幕切换为带语法高亮的 Python 代码;方框短暂突出显示 `import heapq` 和 `def prims_algorithm(graph, start):`。
代码视图仅被部分遍历,片段即结束。
Python 源代码
高亮显示的导入语句
高亮显示的函数定义
图可视化被代码取代。
注意力提示框从导入语句移动到函数签名处。
主题仍为普里姆算法。
该代码对应此前讲解过的贪心过程。
视频从概念演示转向使用基于堆的优先队列进行算法实现。
一个绿色矩形轮廓高亮标出 Python 函数内部的四行初始化代码。
代码块保持静止,仅高亮框的位置发生变化。
Python 代码编辑器视图
绿色高亮矩形
变量 `priority_queue`、`visited`、`mst_edges`、`total_cost`
该高亮覆盖从 00:00 到约 00:17 的初始化语句。
讲解者按高亮行出现的相同顺序逐一介绍每个变量。
在此时间段内,底层代码文本不发生变化。
函数签名 `def prims_algorithm(graph, start):` 始终显示在高亮区域上方。
视觉强调将口头讲解直接对应到定义算法状态的数据结构上。
绿色方框依次下移至 `visited.add(start)`,再移至 `for neighbor, weight in graph[start]:` / `heapq.heappush(...)` 行。
高亮代码展示了从起始顶点进行种子化,并将其关联边压入堆中。
执行 `visited.add(start)` 这一行
执行 `for neighbor, weight in graph[start]:` 循环
执行 `heapq.heappush(priority_queue, (weight, start, neighbor))` 这一行
绿色高亮矩形
高亮区域从初始化代码块移至播种语句。
代码视图在 00:26 附近略微向下滚动,以显示更多循环体内容。
同一段 Python 实现始终保留在屏幕上。
优先队列中元组的格式保持为 `(weight, source, destination)`。
动画标记了从设置阶段到首次贪心前沿构建的过渡。
绿色方框依次高亮显示 `while priority_queue:`、`weight, u, v = heapq.heappop(priority_queue)`、`if v not in visited:`、三条更新语句,以及内部的邻居入队代码块。
当前可见的代码展示了完整的贪心循环及扩展步骤。
当优先队列非空时:
从优先队列中弹出堆顶元素
如果顶点 v 尚未被访问过:
将顶点 v 加入已访问集合
将边 (u, v) 加入最小生成树的边集
总代价增加 weight
内层 `for neighbor, weight in graph[v]:` 循环块
绿色高亮矩形
高亮区域按执行顺序逐组推进,每次覆盖一组语句行。
代码视图自动滚动,使函数的下半部分显现出来。
堆始终优先提供权重最小的候选边。
接受判定依据是顶点 `v` 是否已在 `visited` 集合中。
移动的方框将静态代码转化为算法贪心循环的时间演进过程。
一张米色幻灯片,标题为“时间复杂度”,上面依次显示并逐步化简若干公式。
表达式从 `` 变为 ``,再变为 ``。
标题“时间复杂度”
公式 ``
公式 ``
公式 ``
最初仅显示 ``。
随后追加 `+ `。
最后整个表达式简化为 ``。
幻灯片背景与标题保持不变。
变量 `E` 和 `V` 在整个序列中保持相同的含义。
视觉上的简化过程,对应于口头论述中‘堆操作主导边扫描’这一观点。
同一张幻灯片在时间复杂度结果下方添加了绿色标题 `SPACE COMPLEXITY`。
空间复杂度公式逐项增长,随后化简为 ``。
标题 `SPACE COMPLEXITY`
公式 ``
公式 ``
公式 ``
公式 ``
最终公式 ``
一个新的标题出现在时间复杂度结果的下方。
各项依次加入图存储、已访问集合、优先队列和最小生成树边列表。
求和表达式被简化为总时间复杂度 ``。
先前得到的时间复杂度结果 `` 仍显示在上方。
`V` 和 `E` 继续分别表示顶点数与边数。
分阶段累加的方式明确展示了在渐近简化之前,各数据结构对内存占用的贡献。
讲述者明确对比了边数过少导致不连通、边数过多导致出现环的情形。
有人可能认为生成树的边数可以少于或大于 V - 1 条,但仍为有效生成树。
少于 V - 1 条边无法连接全部 V 个顶点,而多于 V - 1 条边则必然引入环;因此,一棵生成树必须恰好含有 V - 1 条边。
讲解反复将选择范围限定在与当前树相连的边上,再从中选出权重最小的一条。
仅黄色树顶点的边界边在红色选择前被高亮为绿色。
人们可能误以为普里姆算法只是在每一步从整个图中任意挑选权重最小的边。
视频指出,普里姆算法选取的是从当前树跨越到外部顶点的、权重最小的边,而非任意的全局未使用最小权重边。
在 00:36-00:47 处,讲解者说:“将当前节点所连接的所有边压入优先队列。”
屏幕上显示的代码中,在 `heapq.heappush(priority_queue, (weight, v, neighbor))` 前实际包含 `if neighbor not in visited:` 判断。
可从讲解推断:当前节点的每一条关联边都被无条件地压入优先队列。
屏幕上展示的实现仅压入邻居节点尚未进入 `visited` 集合的边;而讲解所述范围比屏幕上显示的代码更宽泛。
该片段描述了返回最小生成树的边集及其总代价,但未说明连通性这一前提条件。
代码本身并未显式检查是否所有顶点均已被访问到。
仅看这段代码片段,可能让人误以为它对任意输入图都总能生成一棵生成树。
分析说明:对于连通的带权图,最小生成树存在;若图不连通,则所示过程生成的是最小生成森林而非单棵生成树。这一条件在视频片段中未被提及。
讲解员将普里姆算法介绍为求解该示例图最小生成树的方法。
普里姆算法被呈现为一种构造最小生成树的方法。
讲解员指出,优先队列用于存储边,以便快速获取权重最小的边。
优先队列是实现普里姆算法中贪心选边步骤所采用的数据结构。
讲解员特别指出,V - 1 条边这一性质适用于任意最小生成树。
边数公式是生成树定义中所包含的结构性质,因而也适用于最小生成树。
该代码定义了 `prims_algorithm` 函数,并使用 `heapq` 模块,配合一个已访问节点集合和一个候选边堆。
这段 Python 代码实现了前文所讲解的普里姆算法步骤。
对示例图的手动执行过程严格遵循前文所述的贪心规则。
该示例在一幅具体的带权图上展示了这一抽象方法。
初始化代码块定义了紧随其后立即使用的容器,包括 `visited.add(start)` 和首个 `heapq.heappush` 循环中用到的容器。
播种步骤必须等待优先队列、已访问集合、边列表以及代价累加器全部创建完毕后才能执行。
首批入队的边填充了 `priority_queue`,随后该队列被 `while priority_queue:` 和 `heapq.heappop(priority_queue)` 消耗。
主贪心循环作用于初始播种步骤所构建的前沿(frontier)。
只有当 `v not in visited` 为真时,才执行 `visited.add(v)`、`mst_edges.append((u, v))`、`total_cost += weight` 以及邻居扩展循环。
仅当最小候选边被弹出且其终点通过已访问测试后,该边才被接受,前沿才得以扩展。
被接受的边与累积的代价分别存入 `mst_edges` 和 `total_cost`,而这二者正是 `return mst_edges, total_cost` 所返回的值。
最终输出依赖于每次接受步骤中所构建的记录。
时间复杂度解释中提到的遍历边并将其压入优先队列的操作,正是循环体内的操作。
该幻灯片从相同的基于堆的操作中推导出 ``。
时间界通过对主循环中反复执行的弹出与压入操作行为进行分析而得到。
空间说明中提到了图、已访问集合、优先队列以及最小生成树边列表。
这些结构恰好是在代码中初始化、并在幻灯片上求和的那些结构。
空间界是通过将初始化阶段引入的数据结构所需的内存开销相加而得出的。
同一张幻灯片在 `SPACE COMPLEXITY` 上方展示了 `TIME COMPLEXITY`,但二者最终表达式不同。
该视频片段区分了运行时开销(主要由堆操作主导)与内存开销(主要由存储图及辅助结构主导)。
开篇解说直接定义了这一概念。
讲解员先说明贪心构造思路,再通过一个示例加以演示。
讲解员借助图的不连通性与环路特性来解释其原理。
讲解员指出,优先队列能够快速检索出权重最小的边。
可见的 Python 代码介绍了实现结构。
本片段未展示完整的函数体。
所选的红色边为 AB、BD、DC、AF,其可见权重分别为2、2、1、3。
初始化语句在屏幕上可见,并配有语音讲解。
讲解员解释了如何添加起始节点并将其邻接边压入堆中。
对应的代码在屏幕上被高亮显示。
代码在从堆中弹出元素后立即显示 `if v not in visited:`。
讲解者指出,该算法会检查当前节点是否已在已访问集合中。
高亮代码块更新 `visited`、`mst_edges`、`total_cost`,并压入新的边。
讲解者明确指出了邻接表表示法这一前提假设。
幻灯片将时间复杂度简化为 ``。
讲解者解释了对边的遍历以及对堆进行对数级次数的插入操作。
已覆盖 · 在初始带权图上对最小生成树的定义。
已覆盖 · 在逐步演示运行之前,对普里姆算法的高层描述。
已覆盖 · 在示例图上逐步动画执行普里姆算法。
已覆盖 · 最终生成树的展示,以及对 V - 1 条边性质的解释。
已覆盖 · 介绍使用 heapq 的 Python 实现;仅可见代码开头部分。
已覆盖 · 代码初始化块逐变量展示并讲解。
已覆盖 · 起始顶点被标记为已访问,其关联边被压入队列;同时申明了邻接表假设。
已覆盖 · while 循环条件、最小边出队操作及已访问检测均被展示并配以旁白说明。
已覆盖 · 接受更新与邻居扩展操作被展示;旁白与代码之间存在的细微差异被记录为一种误解。
已覆盖 · 函数返回语句被高亮显示并加以解释。
已覆盖 · 从代码视图过渡到复杂度幻灯片;除场景切换外,未引入额外数学内容。
已覆盖 · 时间复杂度幻灯片从 `` 逐步推导至 ``,并配有口头论证。
已覆盖 · 空间复杂度幻灯片累加各组成部分的上界,并化简为 ``。