最小生成树算法

211 阅读1分钟

在图论中,一个最小生成树(Minimum Spanning Tree, MST)是指一个连通无向图中包含所有顶点并且边的总权值最小的树。最常用的两种算法来求解这个问题是Prim算法和Kruskal算法。

1. Prim算法

Prim算法是一种贪心算法,通过逐步添加最小权值的边来构建最小生成树。具体步骤如下:

  1. 选择起点:从图中选择一个起点。
  2. 建立起点树:将起点加入树中。
  3. 查找最近点:找到与起点树最近的点,并将其加入树中。
  4. 重复步骤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算法是一种基于并查集的算法,通过不断选择权值最小的边来构建最小生成树。具体步骤如下:

  1. 初始化并查集:将所有点都分为不同的集合。
  2. 遍历所有边:从小到大选择边,并检查是否形成环。
  3. 合并集合:如果不形成环,则将两个集合合并。

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}`);
}