最小生成树(MST)模板

208 阅读2分钟

图的两个基本元素是点和边,因此,可以用两种方法构造最小生成树,prim算法与kruskal算法,均是基于贪心法,即全局最优一定包含局部最优,prim算法的原理是“最近的邻居一定在MST上”,kruskal算法的原理是“最短的边一定在MST上”.

prim算法 O(n2)O(n^2)

  1. 对点进行贪心操作,从任意一个点u开始,把距离它最近的点v加入到MST中;
  2. 把距离{u, v}最近的点w加入到MST中;
  3. 继续这个过程,直到所有的点都在MST中. 其思路与朴素版dijkstra算法相似。
const int N = 510, INF = 0x3f3f3f3f;
int g[N][N]; // 存储图
int dis[N], st[N]; //dis存储图中节点到MST的最短距离,st表示节点是否在MST上

void prim(){
    memset(dis, 0x3f, sizeof dis); // 将所有节点到MST的距离初始化为无穷大
    dis[1] = 0; st[1] = 1; //将节点1放入MST中
    int ans = 0; //记录最小生成树上边的权重
    
    for (int i = 0; i < n; i ++ ){
        int t = -1;
        for (int j = 1; j <= n; j ++ ){
            if (!st[j] && (t == -1 || dis[j] < dis[t])) t = j; // 找到距离MST最近的节点
        }
        
        if (dis[t] == INF) { // 若节点t到MST的最短距离为无穷大,则表示图不连通,不存在最小生成树
            puts("impossible");
            return;
        }
        
        ans += dis[t];
        st[t] = 1; //标记节点t加入MST
        
        for (int j = 1; j <= n; j ++ ){
            if (!st[j]){
                dis[j] = min(dis[j], dis[t] + g[t][j]); //用节点t更新其他节点到MST的距离
            }
        }
    }
    
    cout << ans << endl;
}

int main(){
    cin >> n >> m; //n为节点数, m为边数
    int a, b, c;
    memset(dis, 0x3f, sizeof dis);
    for (int i = 0; i < m; i ++ ){
        cin >> a >> b >> c; //输入m条边的信息
        if (a == b) continue;
        g[a][b] = g[b][a] = min(g[a][b], c); //对于重边情况,取最小值作为边的权重
    }
    
    prim();
}

kruskal算法 O(mlogm)O(mlogm)

  1. 对边进行贪心操作,先将所有边按权值升序排序
  2. 从最短的边开始,把它加入MST中(使用并查集实现)
  3. 在剩下的边中找最短的边,判断其两端的节点是否在MST中,若有一个以上不在,则将其加入MST中
  4. 重复此步骤,直到所有节点都在MST中
const int N = 1e5 + 10, M = 2 * N;
int p[N], cnt, n, m; // p[N]表示节点所属的集合,当所有节点所属集合一致时,最小生成树构建完成,cnt表示生成树中的边的数目,cnt = n - 1时,表示构建完成

struct Edge{
    int a, b, c;
}edge[M]; //稀疏矩阵存边

int find(int x) {return x == p[x] ? x : p[x] = find(p[x]);} //并查集中的查找操作

bool cmp(Edge A, Edge B){
    return A.c < B.c; //自定义排序规则,按边权升序排序
}

void kruskal(){
    int ans = 0;
    for (int i = 0; i < m; i ++ ){
        int a = edge[i].a, b = edge[i].b, c = edge[i].c;
        
        int pa = find(a), pb = find(b); //找到节点a与b所属的集合pa与pb
        
        if (pa != pb){ //若pa与pb不同,则将两个集合合并,并把该条边加入最小生成树中
            p[pb] = pa; //并查集中的合并操作
            cnt ++;
            ans += c;
        }
    }
    
    if (cnt == n - 1) cout << ans << endl; //若最小生成树中边的数量不为n - 1则代表不存在最小生成树
    else puts("impossible");
}

int main(){
    int a, b, c;
    cin >> n >> m;
    for (int i = 0; i < m; i ++ ){
        cin >> a >> b >> c;
        edge[i] = {a, b, c};
    }
    sort(edge, edge + m, cmp); //对所有边按权值升序排序
    
    kruskal();
}