生成樹(Spanning Tree)中任意兩節點路徑唯一,但廣義「生成圖」可能含環。本文實現基於鄰接表的 Dijkstra 算法,支援動態添加/刪除邊的增量更新,適用於網絡路由、拓撲分析場景。
// 邊定義
class Edge {
String from, to;
int cost;
Edge(String from, String to, int cost) { this.from=from; this.to=to; this.cost=cost; }
}
// 圖:鄰接表 Map<節點, List<邊>>
class Graph {
Map> adj = new HashMap<>();
void addEdge(String u, String v, int w) {
adj.computeIfAbsent(u, k -> new ArrayList<>()).add(new Edge(u, v, w));
adj.computeIfAbsent(v, k -> new ArrayList<>()).add(new Edge(v, u, w)); // 無向圖
}
void removeEdge(String u, String v) { /* 從兩側列表移除 */ }
}
class ShortestPath {
private final Graph graph;
private Map dist;
private Map prev; // 前驅節點,用於回溯
public ShortestPath(Graph graph) { this.graph = graph; }
public List findPath(String src, String dst) {
dist = new HashMap<>();
prev = new HashMap<>();
PriorityQueue pq = new PriorityQueue<>(Comparator.comparingInt(n -> n.dist));
dist.put(src, 0);
pq.offer(new Node(src, 0));
Set visited = new HashSet<>();
while (!pq.isEmpty()) {
Node cur = pq.poll();
if (visited.contains(cur.id)) continue;
visited.add(cur.id);
if (cur.id.equals(dst)) break;
for (Edge e : graph.adj.getOrDefault(cur.id, Collections.emptyList())) {
if (visited.contains(e.to)) continue;
int nd = cur.dist + e.cost;
if (nd < dist.getOrDefault(e.to, Integer.MAX_VALUE)) {
dist.put(e.to, nd);
prev.put(e.to, cur.id);
pq.offer(new Node(e.to, nd));
}
}
}
return buildPath(src, dst);
}
private List buildPath(String src, String dst) {
List path = new ArrayList<>();
if (!dist.containsKey(dst)) return path; // 不可達
for (String at = dst; at != null; at = prev.get(at)) path.add(at);
Collections.reverse(path);
return path;
}
static class Node { String id; int dist; Node(String id, int d) { this.id=id; this.dist=d; } }
}
| 策略 | 觸發時機 | 複雜度 | 適用場景 |
|---|---|---|---|
| 全量重算 | 拓撲變更後 | O(E log V) | 節點少、變更頻率低 |
| 增量更新 | 邊權重變化/新增/刪除 | 受影響子圖 | 大型網絡、頻繁變更 |
| 按需計算 + 緩存 | 首次查詢 | 首次 O(E log V),後續 O(1) | 查詢熱點集中、拓撲相對穩定 |
文中提到的「每次問鄰居」屬於**按需計算**,但未緩存會導致重複計算。工程實踐建議:
Map> (源 → 目標 → 下一跳/代價)。Graph g = new Graph();
g.addEdge("A", "B", 1);
g.addEdge("A", "C", 1);
g.addEdge("B", "D", 1);
g.addEdge("C", "D", 1);
g.addEdge("D", "E", 1);
ShortestPath sp = new ShortestPath(g);
System.out.println(sp.findPath("A", "E")); // [A, B, D, E] 或 [A, C, D, E] (代價相同)
System.out.println(sp.findPath("A", "D")); // [A, B, D] 或 [A, C, D]