理论篇
本文用到的实例:
对节点进行编码,定义图的数据结构:
1 | V_node = ['A','B','C','D','E','F'] |
最小生成树
连通图的最小生成树:边的权值最小的生成树,最小生成树不唯一,但最小生成树中权值之和唯一。
求解最小生成树的算法依赖于如下性质:如果U是V的一个子集,且(u,v)是连接U与V-U的最小权值边,则必存在一棵包含(u,v)的最小生成树。
Prim算法——双边袋法
- 问题:给定连通图G,找到一棵最小生成树
- 思路:prim算法和Dijkstra算法非常类似,同样可以用双袋法求解,不同之处在于这里的袋子存放的是边:
- 树边袋:存储的是已纳入最小生成树的边
- 图边袋:存储的是剩余顶点到树袋中所有顶点的最小边的权值
- 更新规则:每次从图袋中选取一个最小边(u,v),移至图袋,用该边的尾部节点更新图袋
1 | 输入:邻接表示的图G,列表表示的顶点V |
- 代码:为优化查找更新效率,图边袋用(u,v,w)的倒查表实现{v:(u,w)}
1 | def Prim(G,V): |
- 分析:
- 时间复杂度:$O(V^2)$,与边无关,适用于稠密图
- 空间复杂度:$O(V)$
最短路径
- BFS:用于求解无权图的单源最短路径
- Dijkstra:用于求解非负权图的单源最短路径(无权图可以看做是一种权值为1的特例)
- Floyd:求解有权图中任意两个顶点间的最短路径
最短路径的重要性质:两点之间的最短路径也包含了路径上其他顶点间的最短路径。
BFS——逐层遍历,记录层数
BFS用于求解无权图的单源最短路径,详情参见BFS。
Dijkstra——双点袋法,移动更新
- 问题:给定一个各边权值皆为非负数的图,求源顶点到图中各个顶点的最短路径;
- 思路:Dijkstra算法可以简单通过“双点袋法”来实现:
1 | 输入:邻接表示的图G,列表表示的顶点V,源节点s |
- 代码:
1 | def Dijkstra(G, V, s): |
- 分析:
- 适用条件:权值非负的有向图或无向图
- 时间复杂度:$O(V^2)$
- 空间复杂度:$O(V)$
Dijkstra算法是本文最重要的算法,因为:
- 如果各边权值为1,则Dijkstra算法可以代替BFS求解无权图的单源最短路径问题;
- 只需对Dijkstra算法稍加修改,就变成了求解最小生成树的Prime算法;
- 对每个顶点应用Dijkstra算法,可以得到任意两个顶点间的最短距离,时间复杂度同样为$O(V^3)$;
Floyd——n 次绕行,更新矩阵
- 问题:给定一个不包含含有负权值回路的图,求任意两个顶点间的最短路径长度
- 思路:矩阵绕行各个顶点
1 | 输入:图G,顶点V |
- 代码:
1 | def Floyd(G,V): |
- 分析:
- 使用条件:允许图中有负权值,但不允许含含有负权值回路
- 时间复杂度:$O(V^3)$
- 空间复杂度:$O(V^2)$
实战篇
[LeetCode 743. 网络延迟时间]
- 问题:
1 | 有 N 个网络节点,标记为 1 到 N。 |
- 代码:
1 | def networkDelayTime(self, times, N, K): |