給定無向圖的邊列表(二元組),求最小生成樹(MST),輸出構成 MST 的邊集合。要求 Java 或 C# 實現,確保無環且連通所有節點。
| 算法 | 時間複雜度 | 適用場景 | 核心數據結構 |
|---|---|---|---|
| Kruskal | O(E log E) | 稀疏圖、邊列表輸入 | 并查集、邊排序 |
| Prim | O(E log V) | 稠密圖、鄰接表/矩陣 | 優先隊列、訪問標記 |
本題輸入為邊列表,Kruskal 更自然。
using System;
using System.Collections.Generic;
using System.Linq;
public class MSTSolver
{
public static List<Tuple<string, string>> Kruskal(List<Tuple<string, string>> edges)
{
// 1. 收集所有節點
var nodes = edges.SelectMany(e => new[] { e.Item1, e.Item2 }).Distinct().ToList();
var nodeToIdx = nodes.Select((n, i) => new { n, i }).ToDictionary(x => x.n, x => x.i);
// 2. 并查集
int[] parent = Enumerable.Range(0, nodes.Count).ToArray();
int[] rank = new int[nodes.Count];
int Find(int x) => parent[x] == x ? x : parent[x] = Find(parent[x]);
void Union(int a, int b)
{
a = Find(a); b = Find(b);
if (a == b) return;
if (rank[a] < rank[b]) parent[a] = b;
else if (rank[a] > rank[b]) parent[b] = a;
else { parent[b] = a; rank[a]++; }
}
// 3. 處理邊(無權重,保持輸入順序或字典序)
var mst = new List<Tuple<string, string>>();
foreach (var e in edges)
{
int u = nodeToIdx[e.Item1], v = nodeToIdx[e.Item2];
if (Find(u) != Find(v))
{
Union(u, v);
mst.Add(e);
if (mst.Count == nodes.Count - 1) break;
}
}
return mst;
}
}
// 使用示例
var input = new List<Tuple<string, string>>
{
Tuple.Create("a","b"), Tuple.Create("a","c"), Tuple.Create("b","c")
};
var result = MSTSolver.Kruskal(input);
result.ForEach(e => Console.WriteLine($"({e.Item1},{e.Item2})"));
import java.util.*;
public class MSTSolver {
public static List<String[]> kruskal(List<String[]> edges) {
// 節點映射
Map<String, Integer> id = new HashMap<>();
int idx = 0;
for (String[] e : edges) {
for (String s : e) if (!id.containsKey(s)) id.put(s, idx++);
}
// 并查集
int[] parent = new int[idx];
int[] rank = new int[idx];
for (int i = 0; i < idx; i++) parent[i] = i;
int find(int x) { return parent[x] == x ? x : (parent[x] = find(parent[x])); }
void union(int a, int b) {
a = find(a); b = find(b);
if (a == b) return;
if (rank[a] < rank[b]) parent[a] = b;
else if (rank[a] > rank[b]) parent[b] = a;
else { parent[b] = a; rank[a]++; }
}
List<String[]> mst = new ArrayList<>();
for (String[] e : edges) {
int u = id.get(e[0]), v = id.get(e[1]);
if (find(u) != find(v)) {
union(u, v);
mst.add(e);
if (mst.size() == id.size() - 1) break;
}
}
return mst;
}
public static void main(String[] args) {
List<String[]> input = Arrays.asList(
new String[]{"a","b"}, new String[]{"a","c"}, new String[]{"b","c"}
);
kruskal(input).forEach(e -> System.out.println("(" + e[0] + "," + e[1] + ")"));
}
}
// 三角形:任選兩邊
Input: (a,b) (a,c) (b,c)
Output: (a,b) (a,c) // 或其他任意兩邊
// 方形+對角線
Input: (1,2) (2,3) (3,4) (4,1) (1,3)
Output: (1,2) (2,3) (3,4) // 權重相同時按序選前三條
Kruskal 算法配合并查集是解決「邊列表求 MST」的標準利器。C# 與 Java 實現核心邏輯一致,差異僅在語法糖與集合庫。掌握并查集模板,即可輕鬆應對各類生成樹、連通分量、動態連通性問題。