zoukankan      html  css  js  c++  java
  • 最小生成树(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;
        }
    }
    

      

  • 相关阅读:
    IOS归档操作
    IOS文件操作
    NSNumber,NSValue,NSData
    dbcp数据库连接池属性介绍
    Spring整合hibernate4:事务管理
    Spring整合hibernate4
    Spring AOP
    Chapter 27 Controlling Animations
    Chapter 23 Core Data
    Chapter 21 WebServices and UIWebView
  • 原文地址:https://www.cnblogs.com/jaszzz/p/12870282.html
Copyright © 2011-2022 走看看