文章目录
- 前言
- 参考目录
- 学习笔记
- 1:介绍
- 1.1:定义
- 1.2:应用
- 2:贪心算法 greedy algorithm
- 2.1:简化假设
- 2.2:切分定理
- 2.3:demo 演示
- 2.4:贪心算法的证明
- 2.5:算法实现简要说明
- 2.6:删除简化假设
- 3:加权边 API
- 3.1:加权边 API (Weighted edge API)
- 3.2:Java 实现
- 3.3:加权无向图的API(Edge-weighted graph API)
- 3.4:Java 实现
- 4:Kruskal 算法
- 4.1:demo 演示
- 4.2:证明
- 4.3:Java 实现
- 4.4:运行时间
- 5:Prim 算法
- 5.1:demo 演示
- 5.2:证明
- 5.3:实现:延迟实现 (lazy implementation)
- 5.3.1:demo 演示
- 5.3.2:Java 实现
- 5.3.3:运行时间
- 5.4:实现:即时实现(eager implementation)
- 5.4.1:demo 演示
- 5.4.2:索引优先队列(Indexed priority queue)
- 5.4.3:运行时间
前言
本篇主要内容包括:贪心算法、加权边 API、Kruskal 算法 以及 Prim 算法。
参考目录
- B站 普林斯顿大学《Algorithms》视频课
(请自行搜索。主要以该视频课顺序来进行笔记整理,课程讲述的教授本人是该书原版作者之一 Robert Sedgewick。) - 微信读书《算法(第4版)》
(本文主要内容来自《4.3 最小生成树》) - 官方网站
(有书本配套的内容以及代码)
学习笔记
注1:下面引用内容如无注明出处,均是书中摘录。
注2:所有 demo 演示均为视频 PPT demo 截图。
注3:如果 PPT 截图中没有翻译,会在下面进行汉化翻译,因为内容比较多,本文不再一一说明。
开篇语:
Today, we’re gonna talk about minimum spanning trees. This is a terrific topic for this course, because it combines a number of classic algorithms with modern data structures to solve a variety of huge problems that are important in practical applications nowadays.
今天,我们要讨论的是最小生成树这一主题,这对于本课程而言是一个绝佳的话题,因为它结合了多种经典算法以及现代数据结构,用于解决当今实际应用中一系列规模庞大且重要的问题。
1:介绍
1.1:定义
定义: 一个图 G 的生成树 T 是指这样一个子图:
- 连通的(Connected):从任一顶点出发可以到达其他所有顶点。
- 无环的(Acyclic):不存在任何闭合的回路。
- 包含所有顶点(Includes all of the vertices):图 G 中的每一个顶点都在生成树 T 中。
1.2:应用
最小生成树(MST)问题是具有广泛实际应用的基础问题,包括但不限于以下几个方面:
- 颗粒化(Dithering)技术。
- 聚类分析(Cluster analysis)。
- 最大瓶颈路径(Max bottleneck paths)问题求解。
- 实时人脸识别验证(Real-time face verification)算法。
- 采用低密度奇偶校验码(LDPC codes)进行错误校正。
- 利用 Rényi 熵进行图像配准(Image registration)。
- 在卫星和航空影像中寻找道路网络(Find road networks)。
- 在蛋白质氨基酸序列测定中减少数据存储量。
- 模拟湍流流体中粒子相互作用的局部性(Model locality of particle interactions in turbulent fluid flows)。
- 以太网桥接自配置协议(Autoconfig protocol)避免网络中形成环路。
- 近似算法用于解决NP难问题(例如,旅行商问题[TSP],Steiner树问题)。
- 各种网络设计(包括通信网络、电力网络、液压网络、计算机网络、道路交通网络等)。
2:贪心算法 greedy algorithm
2.1:简化假设
2.2:切分定理
(这一部分参照书里面的内容)
2.3:demo 演示
对应书本命题 K:
初始状态:
切分后:
边 0 - 2 标记为黑色,继续切分:
边 5 - 7 标记为黑色,继续切分:
边 6 - 2 标记为黑色,继续切分:
边 0 - 7 标记为黑色,继续切分:
边 2 - 3 标记为黑色,继续切分:
边 1 - 7 标记为黑色,继续切分:
边 4 - 5 标记为黑色:
此时图中 8 个顶点,找到了最小权重的 7 条横切边,这就是 MST。
2.4:贪心算法的证明
对应书本命题 K 证明:
2.5:算法实现简要说明
2.6:删除简化假设
3:加权边 API
3.1:加权边 API (Weighted edge API)
3.2:Java 实现
edu.princeton.cs.algs4.Edge
3.3:加权无向图的API(Edge-weighted graph API)
3.4:Java 实现
edu.princeton.cs.algs4.EdgeWeightedGraph
edu.princeton.cs.algs4.EdgeWeightedGraph#addEdge
edu.princeton.cs.algs4.EdgeWeightedGraph#adj
4:Kruskal 算法
4.1:demo 演示
按权重递增顺序考虑边。
- 如果添加下一条边不会形成环,则将其加入树 T 中。
初始状态:
过程比较长,直接引用 官网的图 进行说明:
最终产生的 MST:
4.2:证明
对应书本命题 O 的证明:
4.3:Java 实现
edu.princeton.cs.algs4.KruskalMST
4.4:运行时间
对应书本命题 N:
5:Prim 算法
5.1:demo 演示
- 从顶点 0 开始,并贪婪地构建树 T。
- 将恰好有一个端点在树 T 中的最小权重边添加到 T 中。
- 重复此过程,直到添加了 V-1 条边为止。
初始状态:
首先找到最短(权重最小)横切边 0 - 7 加入 MST:
继续寻找连接 MST 的最短边 1 - 7 加入 MST:
继续寻找连接 MST 的最短边 0 - 2 加入 MST:
继续寻找连接 MST 的最短边 2 - 3 加入 MST:
继续寻找连接 MST 的最短边 5 - 7 加入 MST:
继续寻找连接 MST 的最短边 4 - 5 加入 MST:
继续寻找连接 MST 的最短边 6 - 2 加入 MST:
最终结果:
5.2:证明
命题: [Jarník 1930, Dijkstra 1957, Prim 1959]
Prim 算法可以计算最小生成树(MST)。
证明: Prim 算法是 MST 贪心算法的一个特例。
- 假设边 e 为连接树上顶点与非树顶点的具有最小权重的边。
- 切割集(切分,Cut)由树上连接的所有顶点组成。
- 没有跨越边(横切边)被标记为黑色。
- 没有跨越边(横切边)的权重更低。
对应书本命题 L 的证明:
5.3:实现:延迟实现 (lazy implementation)
**挑战:**找到仅有一端属于集合 T 的最小权重边。
**延迟解决方案:**维持一个优先队列(PriorityQueue),其中包含至少一端点属于集合 T 的边。
- 使用键值对存储边,其中键是边本身,值是边的权重。
- 通过调用优先队列的删除最小元素函数找到下一条要添加到集合 T 中的边 e(记作 v - w)。
- 若边 e 的两个端点 v 和 w 都已被标记为在 T 中,则跳过这条边不处理。
- 否则,假设 w 未被标记(不在 T 中):
- 将所有连接 w 且另一端点目前不在 T 中的边加入到优先队列中;
- 将边 e 加入到集合 T 中,并标记顶点 w 为已在 T 中。
5.3.1:demo 演示
初始状态:
从顶点 0 开始,所有与之相关的边加入到优先队列 PQ,并按照权重排序:
删除 PQ 中最小值边 0 - 7,加入到 MST:
将顶点 7 相关的边加入到优先队列 PQ,并按照权重排序:
删除 PQ 中最小值边 1 - 7,加入到 MST:
将顶点 1 相关的边加入到优先队列 PQ,并按照权重排序:
删除 PQ 中最小值边 0 - 2,加入到 MST:
将顶点 2 相关的边加入到优先队列 PQ(边 1 - 2、2 - 7 已经过时,不需要添加),并按照权重排序:
删除 PQ 中最小值边 2 - 3,加入到 MST:
将顶点 3 相关的边加入到优先队列 PQ(边 1 - 3 已经过时,不需要添加),并按照权重排序:
删除 PQ 中最小值边 5 - 7,加入到 MST:
将顶点 5 相关的边加入到优先队列 PQ(边 1 - 5 已经过时,不需要添加),并按照权重排序:
删除 PQ 中过时的边 1 - 3、1 - 5、2 - 7。
删除 PQ 中最小值边 4 - 5,加入到 MST:
将顶点 4 相关的边加入到优先队列 PQ(边 4 - 7、0 - 4 已经过时,不需要添加),并按照权重排序:
删除 PQ 中过时的边 1 - 2、4 - 7、0 - 4。
删除 PQ 中最小值边 6 - 2,加入到 MST:
此时对于 V 个顶点已经有 V-1 条边,可以停止。
5.3.2:Java 实现
edu.princeton.cs.algs4.LazyPrimMST
edu.princeton.cs.algs4.LazyPrimMST#scan
5.3.3:运行时间
对应书本命题 M:
5.4:实现:即时实现(eager implementation)
**挑战:**找到与树 T 有且仅有一个端点相连的最小权重边。
**即时解决方案:**维护一个优先队列(其中每个顶点最多有一个条目),包含通过边与树 T 相连接的所有顶点,顶点 v 的优先级等于连接 v到 T 的最短边的权重。
- 从优先队列中删除具有最小优先级的顶点 v,并将其关联的边 e = v - w 添加到 T 中。
- 考虑所有与顶点 v 相连的边 e = v - x。
- 如果 x 已经位于树 T 中,则忽略这条边。
- 如果 x 尚未加入优先队列,则将 x 添加到优先队列中。
- 如果经过边 v - x 后,x 到 T 的最短边距离变小,则更新 x 在优先队列中的优先级。
5.4.1:demo 演示
初始状态:
从顶点 0 开始,所有与之相邻的顶点 7、2、4、6 加入到优先队列 PQ,并按照权重排序:
边 0 - 7 权重最小,加入 MST:
继续检查顶点 7,与之相邻的顶点 1、5 加入 PQ(顶点 2、4 已经在 PQ 中),并按照权重排序:
边 1 - 7 权重最小,加入 MST:
继续检查顶点 1,与之相邻的顶点 3 加入 PQ(顶点 5、7 已经在 PQ 中),并按照权重排序:
边 0 - 2 权重最小,加入 MST:
继续检查顶点 2,与之相邻的顶点 3、6 权重变小,更新权重后重新排序:
边 2 - 3 权重最小,加入 MST:
继续检查顶点 3,与之相邻的顶点 6 已经在 PQ 中,无需做任何操作:
边 5 - 7 权重最小,加入 MST:
继续检查顶点 5,与之相邻的顶点 4 权重变小,更新权重后重新排序:
边 4 - 5 权重最小,加入 MST:
继续检查顶点 4,与之相邻的顶点 6 已经在 PQ 中,无需做任何操作:
最后一条边 6 - 2,加入 MST:
最终结果:
5.4.2:索引优先队列(Indexed priority queue)
为优先队列中的每个键关联一个介于 0 和 N - 1 之间的索引。
- 支持插入(insert)操作,即可以将带有特定索引的键值插入队列中。
- 支持删除最小元素(delete-the-minimum)操作,即能够移除并返回当前优先队列中优先级最高的元素。
- 支持基于索引的减小键值(decrease-key)操作,给定键的索引时,允许修改该键对应的值,使其优先级降低。
edu.princeton.cs.algs4.IndexMinPQ
5.4.3:运行时间
核心要点:
- 采用数组实现对稠密图(Dense graphs)而言是最佳方案。
- 对于稀疏图(Sparse graphs),二叉堆在性能上要快得多。
- 在对性能要求极高的情况下,使用四路堆(4-way heap)是值得投入精力提升性能的。
- 斐波那契堆在理论上的优越性虽高,但在实际开发中却未必值得进行具体实现。
(完)