zoukankan      html  css  js  c++  java
  • 最小生成树之Prim算法

           普里姆算法(Prim算法),图论中的一种算法。可在加权连通图里搜索最小生成树。

    意即由此算法搜索到的边子集所构成的树中,不但包含了连通图里的全部顶点。且其全部边的权值之和亦为最小。该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克发现;并在1957年由美国计算机科学家罗伯特·普里姆独立发现。1959年,艾兹格·迪科斯彻再次发现了该算法。因此,在某些场合,普里姆算法又被称为DJP算法、亚尔尼克算法或普里姆-亚尔尼克算法。我们之前介绍的Kruskal算法适用于稀疏图(一般我们觉得满足|E| < V*(V-1)/4时。图为稀疏图, |E|为边的数量,V为顶点数)。

    我们将要介绍的Prim算法则是适用于稠密图(我们这里所说的适用于某种情况。仅仅表示该算法在这个条件下效率最优)。

    1、算法描写叙述

            从单一顶点開始,普里姆算法依照下面步骤逐步扩大树中所含顶点的数目,直到遍及连通图的全部顶点。

    输入:一个加权连通图,当中顶点集合为V,边集合为E
    输出:使用集合Vnew和Enew来描写叙述所得到的最小生成树
    
    算法流程:
    
      1.初始化:Vnew = {x},当中x为集合V中的任一节点(起始点),Enew = {}。
    
      2.反复下列操作,直到Vnew = V:
        <1>在集合E中选取权值最小的边(u, v),当中u为集合Vnew中的元素。而v则是V中没有增加Vnew的顶点(假设存在有多条满足前述条件即具有同样权值的边。则可随意选取当中之中的一个);
        <2>将v增加集合Vnew中,将(u, v)增加集合Enew中。
    

          以下给出一个无向图,每条边上的数字为权值:

         我们任选一个顶点作为起始点。这里我们随便选一个。就以D作为起始点。

    如今集合Vnew = { D }, Enew =  { }。顶点A、B、E和F通过单条边与D相连。A是距离D近期的顶点,因此将A及相应边AD以高亮表示(下同)。由于顶点A是距离集合Vnew近期的点。所以我们将A增加集合。所以如今集合为 Vnew = { A, D}, Enew = { (A, D) }。

          下一个顶点为距离集合Vnew近期的顶点(也就是距离D或A近期的点)。B距D为9,距A为7。E为15,F为6。因此,F距D或A近期。所以我们将顶点F增加集合Vnew,将边(D, F)增加集合 Enew。如今集合变为 Vnew = { A。 D,   F }, Enew = { (A, D) , (D, F) }。

           我们继续反复上面的步骤。

    我们能够发现距离集合Vnew近期的点为B,(A, B)距离为 7 。所一我们将B增加集合Vnew。 将边(A, B)增加集合Enew。如今集合就变为 Vnew = { A, B, D, F }, Enew = { (A, B),  (A, D), (D, F) }。

         我们仅仅要不断的反复上述步骤,非常快我们就找到了该图的最小生成树(如图所看到的)

          有兴趣的朋友,还能够试试用其它顶点作为起点看看答案是否一致。最后你会惊奇的发现不管你取哪一个点。最后的答案都是一致的。

    2、Prim算法的时间复杂度

          Prim算法循环|V| - 1,每次都要寻找距离集合Vnew的最小值。 扫描与一个点所连接的全部边。

    假设使用将一个点全部的边都扫描一遍的算法,则时间复杂度为O(|V|² + |E|)。

    假设我们使用二叉堆来实现查找距离集合Vnew的最小值。则时间复杂度为O(|E| log |V| )。

    假设使用斐波那契堆优化的话,那么时间复杂度将能够近一步优化为O(|E| + |V | log|V|)。

    3*、Prim算法的证明(不感兴趣的能够直接跳过)

    设Prim生成的树为G0
    
    如果存在Gmin使得cost(Gmin)<cost(G0)
    
    则在Gmin中存在(u,v)不属于G0
    
    将(u,v)增加G0中可得一个环,且(u,v)是该环的最长边
    
    这与prim每次生成最短边矛盾
    
    故如果不成立,得证.
    

    4、Prim算法的实现

          这里我们就用一到题目来说明Prim算法的实现 还是畅通project 。大家能够先思考思考,看看能不能依据上面的描写叙述自己实现Prim算法。以下附上这一题的代码,以供參考:

    【未优化版】

    #include <cstdio>
    #include <vector>
    #define INF 0xfffffff
    #define MAXN 100 + 10
    using namespace std;
    struct Vex{
        int v, weight;
        Vex(int tv, int tw):v(tv), weight(tw){}
    };
    vector<Vex> graph[MAXN];
    bool inTree[MAXN];
    int mindist[MAXN];
    
    void Init(int n){
        for(int i = 1; i <= n; i++){
            mindist[i] = INF;
            inTree[i] = false;
            graph[i].clear();
        }
    }
    
    int Prim(int s, int n){
        int addNode, tempMin, tempVex ,ret = 0;
        //将顶点S增加集合Vnew
        inTree[s] = true;
        //初始化,各点到集合Vnew的距离, 数组mindist表示各点到集合Vnew的最小距离
        for(unsigned int i = 0; i < graph[s].size(); i++)
            mindist[graph[s][i].v] = graph[s][i].weight;
        //由于还有n-1个点没有增加集合Vnew。所以还要进行n-1次操作
        for(int NodeCount = 1; NodeCount <= n-1; NodeCount++){
            tempMin = INF;
            //在还没有增加集合Vnew的点中查找距离集合Vnew最小的点
            for(int i = 1; i <= n; i++){
                if(!inTree[i] && mindist[i] < tempMin){
                    tempMin = mindist[i];
                    addNode = i;
                }
            }
            //将距离集合Vnew距离最小的点增加集合Vnew
            inTree[addNode] = true;
            //将新增加边的权值计入ret
            ret += tempMin;
            //更新还没有增加集合Vnew的点 到 集合Vnew的距离
            for(unsigned int i = 0; i < graph[addNode].size(); i++){
                tempVex = graph[addNode][i].v;
                if(!inTree[tempVex] && graph[addNode][i].weight < mindist[tempVex]){
                    mindist[tempVex] = graph[addNode][i].weight;
                }
            }
        }
        return ret;
    }
    
    int main(){
        int n;
        int v1, v2, weight;
        while(scanf("%d", &n), n){
            Init(n);
            for(int i = 0; i < n*(n-1)/2; i++){
                scanf("%d%d%d", &v1, &v2, &weight);
                graph[v1].push_back(Vex(v2, weight));
                graph[v2].push_back(Vex(v1, weight));
            }
            printf("%d
    ", Prim(1, n));
        }
        return 0;
    }
    

    【堆优化版】

    #include <cstdio>
    #include <vector>
    #include <queue>
    #define INF 0xfffffff
    #define MAXN 100 + 10
    using namespace std;
    struct Vex{
        int v, weight;
        Vex(int tv = 0, int tw = 0):v(tv), weight(tw){}
        bool operator < (const Vex& t) const{
            return this->weight > t.weight;
        }
    };
    vector<Vex> graph[MAXN];
    bool inTree[MAXN];
    int mindist[MAXN];
    
    void Init(int n){
        for(int i = 1; i <= n; i++){
            mindist[i] = INF;
            inTree[i] = false;
            graph[i].clear();
        }
    }
    
    int Prim(int s, int n){
        priority_queue<Vex> Q;
        Vex temp;
        //res用来记录最小生成树的权值之和
        int res = 0;
        //将s增加集合Vnew。并更新与点s相连接的各点到集合Vnew的距离
        inTree[s] = true;
        for(unsigned int i = 0; i < graph[s].size(); i++){
            int v = graph[s][i].v;
            if(graph[s][i].weight < mindist[v]){
                mindist[v] = graph[s][i].weight;
                //更新之后。增加堆中
                Q.push(Vex(v, mindist[v]));
            }
        }
        while(!Q.empty()){
            //取出到集合Vnew距离最小的点
            temp = Q.top();
            Q.pop();
            int addNode = temp.v;
            if(inTree[addNode]) continue;
            inTree[addNode] = true;
            res += mindist[addNode];
            //更新到集合Vnew的距离
            for(unsigned int i = 0; i < graph[addNode].size(); i++){
                int tempVex = graph[addNode][i].v;
                if(!inTree[tempVex] && mindist[tempVex] > graph[addNode][i].weight){
                    mindist[tempVex] = graph[addNode][i].weight;
                    Q.push(Vex(tempVex, mindist[tempVex]));
                }
            }
        }
        return res;
    }
    
    int main(){
        int n;
        int v1, v2, weight;
        while(scanf("%d", &n), n){
            Init(n);
            for(int i = 0; i < n*(n-1)/2; i++){
                scanf("%d%d%d", &v1, &v2, &weight);
                graph[v1].push_back(Vex(v2, weight));
                graph[v2].push_back(Vex(v1, weight));
            }
            printf("%d
    ", Prim(1, n));
        }
        return 0;
    }
    

          假设不了解priority_queue的朋友能够參考:Here

    【斐波那契堆优化】

          先挖个坑,以后再填,有兴趣的朋友能够自行完好。

          想找一些题练练手朋友,能够移步到这里:图论题目分类

    
  • 相关阅读:
    CDOJ 1270 Playfair(模拟)
    HDU 2209 翻纸牌游戏(DFS)
    HDU 1241 Oil Deposits(DFS)
    pta 01-复杂度2 Maximum Subsequence Sum (25分)
    poj 1469 COURSES 二分匹配 dfs
    01-复杂度1 最大子列和问题 (20分)分治
    poj 1325 Machine Schedule 二分图匹配+DFS实现
    zoj 1654 Place the Robots 二分图匹配DFS实现
    图论笔记-第七章
    hdu 5423 Rikka with Tree DFS
  • 原文地址:https://www.cnblogs.com/zhchoutai/p/7289799.html
Copyright © 2011-2022 走看看