• 最小生成树(MST, Minimum Spanning Tree)


    一、概述

    最小生成树问题顾名思义,概括来说就是路修的最短。

    接下来引入几个一看就明白的定义:

    最小生成树相关概念:

    带权图:边赋以权值的图称为网或带权图,带权图的生成树也是带权的,生成树T各边的权值总和称为该树的权。

    最小生成树(MST):权值最小的生成树。

    最小生成树的性质:假设G=(V,E)是一个连通网,U是顶点V的一个非空子集。若(u,v)是一条具有最小权值的边,其中u∈U,v∈V-U,则必存在一棵包含边(u,v)的最小生成树。

    完成构造网的最小生成树必须解决下面两个问题:

    • 尽可能选取权值小的边,但不能构成回路;
    • 选取n-1条恰当的边以连通n个顶点;

    prim算法适合稠密图,kruskal算法适合简单图。

    二、Prim算法

    首先,假设有一棵只包含一个顶点v的树T,然后贪心地选取T和其它顶点之间相连的最小权值的边,并把它加到T中,不断进行这个操作,就可以得到一棵生成树了,可证这是一棵最小生成树

    如何查找最小权值的边?假设已经求得的生成树的顶点的集合是X,把X集合和顶点v连接的边的最小权值记为mincost[v],在向X里添加顶点u时,只需要查看和u相连的边就可以了,对于每条边,更新mincost[v]=min(mincost[v],边(u,v)的权值)
    如果每次都遍历为包含在X中的点的mincost[v],需要O(|V|2)的时间,不过和Dijkstra一样我们可以使用堆来维护mincost,时间复杂度就是O(|E|log|V|) 

    #include <cstdio>
    #include <queue>
    #include <vector>
    #include <utility>
    
    using namespace std;
    
    typedef pair<int, int> P;
    struct Edge
    {
        int to, cost;
    };
    
    const int MAX_V=5000;
    const int INF=0x3f3f3f3f;
    int V, E, N;
    vector<Edge> G[MAX_V];
    int res;
    int mincost[MAX_V];
    bool used[MAX_V];
    
    void prim()
    {
        for (int i=0; i<V; i++)
        {
            mincost[i]=INF;
            used[i]=false;
        }
        mincost[0]=0;
        priority_queue<P, vector<P>, greater<P> > que;
        que.push(P(0, 0));
        while (!que.empty())
        {
            P p=que.top(); que.pop();
            int v=p.second;
            if (mincost[v]<p.first) continue;
            res+=mincost[v];
            N++;
            used[v]=true;
            for (int i=0; i<G[v].size(); i++)//加入顶点v时只要查看和顶点v相连的边即可 
            {
                if (mincost[G[v][i].to]>G[v][i].cost && !used[G[v][i].to])//更新mincost,入队 
                {
                    mincost[G[v][i].to]=G[v][i].cost;
                    que.push(P(G[v][i].cost, G[v][i].to));
                }
            }
        }
    }
    
    int main()
    {
        scanf("%d %d", &V, &E);
        for (int i=0; i<E; i++)
        {
            int s, t, c;
            scanf("%d %d %d", &s, &t, &c);
            Edge e;
            e.to=t-1; e.cost=c;
            G[s-1].push_back(e);
            e.to=s-1;
            G[t-1].push_back(e);
        }
        prim();
        if (N <V) puts("orz");
        else printf("%d
    ", res);
    }
    

      

    三、kruskal算法

    kruskal远离更为简单粗暴,但是需要借助并查集这一知识,来判断是否存在圈。

    Kruskal算法按照边的权值的大小从小到大查看一遍,如果不产生圈(重边等也算在内),就把当前这条边加入到生成树中,一共加V-1条边。

    如何判断是否产生圈? 假设现在要把连接顶点u和顶点v的边e加入生成树中。如果加入之前u和v不在同一个连通分量里,那么加入e也不会产生圈,反之,如果u和v在同一个连通分量里,那么一定会产生圈,可以用并查集高效地判断是否属于同一个连通分量

    Kruskal在边的排序上最费时,算法的复杂度是O(|E|log|V|). 

    #include <cstdio>
    #include <queue>
    #include <vector>
    #include <algorithm>
    #include <cctype>
    #define num s-'0'
    
    using namespace std;
    
    struct edge
    {
        int u;
        int v;
        int cost;
    };
    
    const int MAX_V=5000;
    const int MAX_E=200000;
    const int INF=0x3f3f3f3f;
    int V,E,n;
    edge es[MAX_E];
    int res;
    int par[MAX_V];
    int r[MAX_V];
    
    void kruskal();
    void init();
    int find(int);
    void unite(int, int);
    bool same(int, int);
    
    void read(int &x){
        char s;
        x=0;
        bool flag=0;
        while(!isdigit(s=getchar()))
            (s=='-')&&(flag=true);
        for(x=num;isdigit(s=getchar());x=x*10+num);
        (flag)&&(x=-x);
    }
    
    void write(int x)
    {
        if(x<0)
        {
            putchar('-');
            x=-x;
        }
        if(x>9)
            write(x/10);
        putchar(x%10+'0');
    }
    
    bool compare(const edge &e1, const edge &e2)
    {
        return e1.cost<e2.cost;
    }
    
    int main()
    {
        read(V);read(E);
        for (int i=0; i<E; i++)
        {
            read(es[i].u);read(es[i].v);read(es[i].cost);
            es[i].u--;es[i].v--;
        }
        kruskal();
        write(res);
        putchar('
    ');
    }
    
    void init()
    {
        for (int i=0; i<V; i++)
        {
            par[i]=i;
            r[i]=0;
        }
    }
    
    int find(int x)
    {
        if (par[x]==x) return x;
        return par[x]=find(par[x]);
    }
    
    void unite(int x, int y)
    {
        x=find(x);
        y=find(y);
        if (x==y) return;
        if (r[x]<r[y]) par[x]=y;
        else
        {
            par[y]=x;
            if (r[x]==r[y]) r[x]++; 
        } 
    }
    
    bool same(int x, int y)
    {
        return (find(x)==find(y));
    }
    
    void kruskal()
    {
        sort(es, es+E, compare);
        init();
        int n=0;
        for (int i=0; i<E; i++)
        {
            edge e=es[i];
            if (!same(e.u, e.v))
            {
                unite(e.u, e.v);
                res+=e.cost;
                ++n;
            }
            if (n==V-1) break;
        }
    }
    

      

  • 相关阅读:
    跃迁方法论 Continuous practice
    EPI online zoom session 面试算法基础知识直播分享
    台州 OJ 2648 小希的迷宫
    洛谷 P1074 靶形数独
    洛谷 P1433 DP 状态压缩
    台州 OJ FatMouse and Cheese 深搜 记忆化搜索
    台州 OJ 2676 Tree of Tree 树状 DP
    台州 OJ 2537 Charlie's Change 多重背包 二进制优化 路径记录
    台州 OJ 2378 Tug of War
    台州 OJ 2850 Key Task BFS
  • 原文地址:https://www.cnblogs.com/jaszzz/p/12870282.html
Copyright © 2020-2023  润新知