在图论中,一个最小生成树(Minimum Spanning Tree, MST)是指一个连通无向图中包含所有顶点并且边的总权值最小的树。最常用的两种算法来求解这个问题是Prim算法和Kruskal算法。
1. Prim算法
Prim算法是一种贪心算法,通过逐步添加最小权值的边来构建最小生成树。具体步骤如下:
- 选择起点:从图中选择一个起点。
- 建立起点树:将起点加入树中。
- 查找最近点:找到与起点树最近的点,并将其加入树中。
- 重复步骤3:直到所有点都加入树中。
JavaScript实现
class Graph {
constructor(vertices) {
this.V = vertices; // 顶点数量
this.graph = []; // 存储图的边
}
addEdge(u, v, w) {
this.graph.push({ u, v, w });
}
findMinKey(key, mstSet) {
let min = Infinity;
let minIndex = -1;
for (let v = 0; v < this.V; v++) {
if (mstSet[v] === false && key[v] < min) {
min = key[v];
minIndex = v;
}
}
return minIndex;
}
primMST() {
const parent = Array(this.V).fill(-1); // 存储生成树
const key = Array(this.V).fill(Infinity); // 存储最小权重
const mstSet = Array(this.V).fill(false); // 记录顶点是否在生成树中
key[0] = 0; // 从第一个顶点开始
parent[0] = -1; // 第一个顶点是根节点
for (let count = 0; count < this.V - 1; count++) {
const u = this.findMinKey(key, mstSet);
mstSet[u] = true; // 将选中的顶点加入生成树
for (let edge of this.graph) {
if (edge.u === u || edge.v === u) {
const v = edge.u === u ? edge.v : edge.u;
if (!mstSet[v] && edge.w < key[v]) {
key[v] = edge.w;
parent[v] = u;
}
}
}
}
this.printMST(parent);
}
printMST(parent) {
console.log('边 \t 权重');
for (let i = 1; i < this.V; i++) {
console.log(
`${parent[i]} - ${i} \t ${
this.graph.find(
(edge) =>
(edge.u === parent[i] && edge.v === i) ||
(edge.u === i && edge.v === parent[i])
).w
}`
);
}
}
}
// 示例使用
const g = new Graph(5);
g.addEdge(0, 1, 2);
g.addEdge(0, 3, 6);
g.addEdge(1, 2, 3);
g.addEdge(1, 3, 8);
g.addEdge(1, 4, 5);
g.addEdge(2, 4, 7);
g.addEdge(3, 4, 9);
g.primMST();
// 输出
// 边 权重
// 0 - 1 2
// 1 - 2 3
// 0 - 3 6
// 1 - 4 5
2. Kruskal算法
Kruskal算法是一种基于并查集的算法,通过不断选择权值最小的边来构建最小生成树。具体步骤如下:
- 初始化并查集:将所有点都分为不同的集合。
- 遍历所有边:从小到大选择边,并检查是否形成环。
- 合并集合:如果不形成环,则将两个集合合并。
JavaScript实现
class UnionFind {
constructor(n) {
this.parent = Array(n)
.fill(0)
.map((_, index) => index);
this.rank = Array(n).fill(0); // 用于优化合并操作
}
// 查找节点 x 的根节点,并进行路径压缩
find(x) {
if (this.parent[x] !== x) {
this.parent[x] = this.find(this.parent[x]);
}
return this.parent[x];
}
// 合并节点 x 和 y,采用按秩合并
union(x, y) {
let rootX = this.find(x);
let rootY = this.find(y);
if (rootX !== rootY) {
if (this.rank[rootX] > this.rank[rootY]) {
this.parent[rootY] = rootX;
} else if (this.rank[rootX] < this.rank[rootY]) {
this.parent[rootX] = rootY;
} else {
this.parent[rootY] = rootX;
this.rank[rootX] += 1;
}
}
}
// 检查两个节点是否在同一集合中
connected(x, y) {
return this.find(x) === this.find(y);
}
}
class Graph {
constructor(vertices) {
this.vertices = vertices; // 节点数量
this.edges = []; // 存储图的所有边
}
// 添加一条边,包含两个节点和边的权重
addEdge(u, v, weight) {
this.edges.push({ u, v, weight });
}
// Kruskal 算法,返回最小生成树
kruskal() {
// 按权重排序所有的边
this.edges.sort((a, b) => a.weight - b.weight);
const uf = new UnionFind(this.vertices);
const mst = []; // 最小生成树的边
// 遍历所有的边
for (let edge of this.edges) {
const { u, v, weight } = edge;
// 如果 u 和 v 不在同一个集合中,说明不会形成环,加入最小生成树
if (!uf.connected(u, v)) {
uf.union(u, v);
mst.push(edge);
}
}
return mst;
}
}
// 创建一个包含 4 个节点的图
const g = new Graph(5);
g.addEdge(0, 1, 2);
g.addEdge(0, 3, 6);
g.addEdge(1, 2, 3);
g.addEdge(1, 3, 8);
g.addEdge(1, 4, 5);
g.addEdge(2, 4, 7);
g.addEdge(3, 4, 9);
// 计算最小生成树
const mst = g.kruskal();
// 输出最小生成树的边
console.log('边 \t 权重');
for (let edge of mst) {
console.log(`${edge.u} - ${edge.v} \t ${edge.weight}`);
}