第二节 贪心法应用
例题3 哈夫曼编码
本题为自学内容+题解作业,建议参考课本内容或者这个视频
例题4 单源最短路径
单源最短路径(dijkstra)算法为本章重点
【问题描述】
设 G(V,E) 是一个带权有向图,其中每条边的权值是非负实数 。给定图中的一个点 S∈V,称之为源点,现要求 S 到所有其他顶点 T (T∈V,T≠S) 之间的最短路径长度。路径长度是指一条路径所经过的所有边的边权值之和。
【算法思路】
Dijkstra算法是解单源最短路径问题的贪心算法。其基本思想是,设置顶点集合并不断地做贪心选择来扩充这个集合。当一个顶点已求得从源到该顶点的最短路径时,我们便把该顶点加入到集合中来。从初始的时候,集合中仅含有源点S,到最后计算完毕集合包含了所有从源点可到达的终点。同时我们把结果用一个数组 dist 记录。Dijkstra算法每次从剩余顶点(即未加入集合的顶点)中取出具有最短路径长度的顶点u,将u添加到集合中,同时对数组 dist进行必要的修改。一旦集合包含了所有图中顶点,dist就记录了从源到所有其他顶点之间的最短路径长度。
我们结合以上算法思想把Dijkstra算法进一步规范描述如下:
算法4.3 Dijkstra算法实现:
输入:一个有向图G,若G中有两个顶点s,u存在一条边,长度用d(s,u)表示
设dist[u]为从起点S到顶点u的最短路径长度,初始时dist[S]=0,对于所有其他未处理顶点u,dist[u]=INF(即 ∞ )
然后我们将图的顶点进行一下分类,对于每个顶点,必定属于以下三个分类之一:
红点:最短路径长度确定的顶点集合,从源点到该点的最短路径长度为dist[u]
绿点:访问到但最短路径未确定的顶点集合,当前已知最短长度保存在dist[u]
白点:未访问到的顶点集合,其长度dist[u]=INF
最开始时,设源点S为绿点,其他所有顶点均为白点
Dijkstra算法更新顶点最短路径长度的具体步骤如下:
1. 在绿点中取出一个长度最短的顶点s,作为当前搜索起点
2. 遍历所有从s出发可以到达的相邻顶点u:
2.1 若u已经为红点,不做任何处理;
2.2 若u为白色顶点,将其取出放入绿色顶点,并更新dist[u]=dist[s]+d(s,u)
2.3 若u为绿色顶点,检查dist[s]+d(s,u)是否比当前dist[u]更短,如果是则
更新当前dist[u]的长度
3. 完成对s所有相邻顶点的搜索,将s加入红点集合
4. 循环重复执行以上步骤1-3,直到不存在绿点(若此时还存在白点,这些白点必定是
源点无法到达的。
5. 对于所有的红点u,输出dist[u]为最终结果(源点到u的最短路径长度)
Java代码实现:
import java.util.Arrays;
// 最短路径Dijkstra算法实现代码
public class ShortestPathSolution {
// 定义顶点的访问状态:
// RED(红):已访问且已确定最短路径
// GREEN(绿): 已访问但未确定当前长度为最短
// WHITE(白): 未访问
static final int RED = 2;
static final int GREEN = 1;
static final int WHITE = 0;
/**
* 计算最短路径
* @param n 表示最多总共有n个顶点
* @param edges 所有路径信息,以二维数组给出,
* 数组元素edges[i] = [s, t, w] 表示从 s 到 t 存在权值为 w 的路径
* @param src 起点编号
* @return 输出从起点到所有顶点的路径长度,并返回最后一个路径长度
*/
public int findAllShortestPath(int n, int[][] edges, int src) {
// dist[x]: 保存起点到 x 的最短路径长度
// 初始值为无穷大
int[] dist = new int[n + 1];
Arrays.fill(dist, Integer.MAX_VALUE);
// visit[x]表示 x 的访问状态:
// 0(白)-未访问(初始值);
// 1(绿)-已访问未确定;
// 2(红)-已确定最短长度
int[] visit = new int[n + 1];
// 初始时只有起点被访问到,设置路径长度为0
visit[src] = GREEN;
dist[src] = 0;
// 执行dijkstra搜索最短路径
for (int i = 0; i < n; i++) {
// 搜索所有的绿点,找到路径最短的一个
// 标记为红点,并作为当前搜索点
int s = 0;
for (int j = 1; j <= n; j++)
if (visit[j] == GREEN && dist[j] < dist[s])
s = j;
// 如果没有更多可访问的顶点则直接退出
if (s == 0) break;
// 标记 s 为当前顶点,设置访问状态为红色并开始搜索
visit[s] = RED;
// 下面这行代码输出中间过程,可用于调试
// System.out.println("s=" + s + ", " + dist[s]);
// 枚举当前顶点 s 的邻接路径,发现新的路径并更新维护排序数组
for (int[] ed : edges) {
int u = ed[0], v = ed[1], w = ed[2];
// 当前边是 s 的邻边,且目标顶点未完成搜索(不是红色)
if (s == u && visit[v] != RED) {
// 计算从起点经过s到达v的路径长度
int d = dist[s] + w;
// 计算出一条更短的路径长度,更新路径长度及访问状态
if (d < dist[v]) {
dist[v] = d;
visit[v] = GREEN;
}
}
}
}
// 输出结果并返回
int ans = -1;
System.out.println("所有的顶点路径信息:");
for (int i = 1; i <= n; i++) {
System.out.println("目标顶点 " + i + " -> " + dist[i]);
ans = Math.max(ans, dist[i]);
}
return ans;
}
public static void main(String[] args) {
ShortestPathSolution sln = new ShortestPathSolution();
int[][] edges = {{1,2,2}, {1,5,8},{1,4,3},{2,3,5},{3,5,1},{4,3,2},{4,5,6}};
int n = 5, src = 1;
int ans = sln.findAllShortestPath(n, edges, src);
System.out.println("搜索完毕,最远顶点路径为:" + ans);
}
}
Python3代码实现
# 最短路径Dijkstra算法实现代码
# 定义顶点的访问状态:
# RED(红):已访问且已确定最短路径
# GREEN(绿): 已访问但未确定当前长度为最短
# WHITE(白): 未访问
RED = 2
GREEN = 1
WHITE = 0
## 计算最短路径
## n 表示最多总共有n个顶点
## edges 所有路径信息,以二维数组给出,
## 数组元素edges[i] = [s, t, w] 表示从 s 到 t 存在权值为 w 的路径
## src 起点编号
## 返回:输出从起点到所有顶点的路径长度,并返回最后一个路径长度
def findAllShortestPath(n: int, edges: list[list[int]], src: int) -> int:
# dist[x]: 保存起点到 x 的最短路径长度
# 初始值为理论上的最大值(所有边长加起来再加1)
INF = sum(e[2] for e in edges) + 1
dist = [INF] * (n + 1)
# visit[x]表示 x 的访问状态:
# 0(白)-未访问(初始值)
# 1(绿)-已访问未确定
# 2(红)-已确定最短长度
visit = [WHITE] * (n + 1)
# 初始时只有起点被访问到,设置路径长度为0
visit[src] = visit[0] = GREEN
dist[src] = 0
# 执行dijkstra搜索最短路径
for _ in range(n):
# 搜索所有的绿点,找到路径最短的一个
# 标记为红点,并作为当前搜索点
s = min([x for x in range(0, n + 1) if visit[x] == GREEN], key=lambda x: dist[x])
# 如果没有更多可访问的顶点则直接退出
if (s == 0): break
# 标记 s 为当前顶点,设置访问状态为红色并开始搜索
visit[s] = RED
# 下面这行代码输出中间过程,可用于调试
print('s=', s, dist[s])
# 枚举当前顶点 s 的邻接路径,发现新的路径并更新维护排序数组
for u, v, w in edges:
# 当前边是 s 的邻边,且目标顶点未完成搜索(不是红色)
if s == u and visit[v] != RED:
# 计算从起点经过s到达v的路径长度
d = dist[s] + w
# 计算出一条更短的路径长度,更新路径长度及访问状态
if d < dist[v]:
dist[v] = d
visit[v] = GREEN
# 输出结果并返回
print('所有的顶点路径信息:')
for i, d in enumerate(dist):
if visit[i] != RED: continue
print('目标顶点', i, '->', d)
return max(dist[1:])
# main
n, src = 5, 1
edges = [[1,2,2],[1,5,8],[1,4,3],[2,3,5],[3,5,1],[4,3,2],[4,5,6]]
ans = findAllShortestPath(n, edges, src)
print('搜索完毕,最远顶点路径为:', ans)
例题5 最小生成树
首先要理清概念,弄清楚什么是:
- 树 (1 连通,2 无环)
- 生成树 (连通图的一个子集,且必须是树)
- 最小生成树(连通图的一个生成树,且边权值之和为最小)
解决最小生成树问题的常见算法:
- Prim
- Kruskal