• 最小生成树--prim学习笔记


    最小生成树 prim

    问题引入:假设要在n个城市之间建立通信联络网,则连通n个城市需要n-1条路线,这是怎么样能在最节省经费的前提下建立这个通信网?

    可以用联通网来表示n个城市,以及城市间的通信线路。其中顶点代表城市,边代表城市间线路,边的权值表示相应代价。n个顶点的联通网可以建立许多不同的生成树,其中要求的便是代价和最小的生成树。也称最小生成树。


     构建生成树的算法多数利用了最小生成树的MST性质:

     假设N=VE)是一个联通网,U是顶点集V的一个非空子集。若(uv)是一条具有最小权值的边,其中uUvV-U,则必定存在一颗包含边(uv)的最小生成树

    反证法:假设N的任何最小生成树都不包含(uv),设T是联通网上的一颗最小生成树,当将边(uv)加入T中,由生成树的定义,T中必定包含(uv)的回路,由于T是生成树,则T上必存在另一条边(u`,v`),其中u`Uv`V-U,且uu`之间,vv`之间均有路径相通。删去边(u`v`,便可消除上述回路,同时得到另一颗生成树T`,因为(uv)的权值不高于(u`v`),则T`的权值不高于TT`是包含(uv)的一颗最小生成树,由此和假设矛盾。


    Prim算法

     步骤:

     假设存在N=VE)是联通网。

     1、N分成两个集合 S={u0}  T={V-S}  TEN最小生成树边的集合

     2、在所有的uSvT的边(uv)中找到一条权值最小的边(u0v0)并入集合(u0v0)并入集合TE,点v0并入S

     3、重复步骤2,直到S=V为止

     此时TE中的必定有n-1条边,则T=VTE)是n的最小生成树

     代码实现:以模板题为例(POJ - 1287 B - Networking)

    //邻接矩阵存储图
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    const int MAX=60;
    const int INF=0x3f3f3f3f;
    int n,m,a,b,len;
    int map[MAX][MAX];                                //邻接矩阵存图            
    int dist[MAX];                                    //记录(u,v)的距离大小,其中u∈S, v∈T 
    bool vis[MAX];                                    //记录点是否已经在集合S中 
    int prim()
    {
        for(int i=1;i<=n;i++)                        //初始化 
        {
            dist[i]=INF;
            vis[i]=false;
        }
        dist[1]=0;                                    //从点1开始生成最小生成树 
        int min,pos,ans=0;
        for(int i=1;i<=n;i++)
        {
            min=INF;
            for(int j=1;j<=n;j++)                    //找到(u,v)的最小值(u0,v0)     该边一定是最小生成树中的一条边 
            {
                if(!vis[j]&&min>dist[j])
                {
                    min=dist[j];
                    pos=j;
                }
            }
            ans+=min;                                //加入生成树 
            vis[pos]=true;                            //将点 v0加入S集合中 
            for(int j=1;j<=n;j++)
            {
                if(!vis[j]&&dist[j]>map[pos][j])    //更新dist,看新集合S中是否存(u,v)更小的值,并取代 
                    dist[j]=map[pos][j];
            }
        }
        return ans;
    }
    int main()
    {
        while(1)
        {
            scanf("%d",&n);                                    //顶点数 
            if(n==0)break;
            scanf("%d",&m);                                    //边数 
            for(int i=1;i<=n;i++)                            //初始化 
                for(int j=1;j<=n;j++)
                    map[i][j]=INF;
            while(m--)
            {
                scanf("%d%d%d",&a,&b,&len);
                if(map[a][b]>len)
                    map[a][b]=map[b][a]=len;
            }
            int ans=prim();
            printf("%d
    ",ans);
        }
        return 0;
    }

    算法分析

     算法中有两个循环,一个初始化循环,第二个循环包含两个内循环:1、在dist中寻找最小值     2、重新选择具有最小权值的边

    prim算法的时间复杂度为O(n2),与图的边数无关,适用于求稠密图(点少边多)的最小生成树。 


    prim堆优化

    根据上面的分析可以知道prim花费时间在dist中寻找边的最小值,和更新最小权值边,外层的n次是不可避免的,因为需要将n个点都加入最小生成数中。因此能够优化的地方

    是寻找最小边的值。这里我们可以用一个优先队列(小根堆)来维护(u,v)抵达生成树边的值,每次取出队列最前且合法的边加到生成树中就可以,不合法的在过程中丢弃。

    代码实现

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    using namespace std;
    const int MAX=60;
    const int MAXN=30000;
    const int INF=0x3f3f3f3f;
    int head[MAX],cnt=0;
    int a,b,len,n,m;
    int dist[MAX];
    bool vis[MAX];
    struct Edge{
        int next,to,val;
    }Edge[MAXN];
    inline void add(int u,int v,int w)
    {
        Edge[cnt].to=v;
        Edge[cnt].val=w;
        Edge[cnt].next=head[u];    
        head[u]=cnt++;
    }
    struct node{                
        int pos,dist;
        node(){}
        node(int p,int d)
        {
            dist=d;pos=p;
        }
        bool operator < (const node &rhs)const                    //重载< 变成小根堆 
        {
            return dist>rhs.dist;
        }
    };
    int prim()
    {
        priority_queue<node>que;    
        memset(vis,false,sizeof(vis));                            //初始化
         
        for(int i=head[1];i!=-1;i=Edge[i].next)                    //将第一个点相连的边加入优先队列中 
            que.push(node(Edge[i].to,Edge[i].val));
        vis[1]=true;                                            //第一个点在集合S中 
        int num=n-1,ans=0;
        while(num--)
        {
            node temp=que.top();que.pop();                        //弹出小根堆第一个元素,这里第一个元素的dist最小 
            while(vis[temp.pos])                                //但是并不一定可以用,若这个点已经在集合S中则不需要 
            {
                temp=que.top();que.pop();
            }
            ans+=temp.dist;                                        //将找到的边加入生成树中 
            vis[temp.pos]=true;                                    //标记在S中的点(即加入生成树中的点) 
            
            for(int i=head[temp.pos];i!=-1;i=Edge[i].next)        //更新距离 
                if(!vis[Edge[i].to])
                    que.push(node(Edge[i].to,Edge[i].val));
        }
        return ans;
    }
    int main()
    {
        while(1)
        {
            memset(head,-1,sizeof(head)),cnt=0;                    //初始化 
            scanf("%d",&n);
            if(n==0)break;
            scanf("%d",&m);
            while(m--)
            {
                scanf("%d%d%d",&a,&b,&len);
                add(a,b,len);
                add(b,a,len);
            }
            int ans=prim();
            printf("%d
    ",ans);
        }
        return 0;
    }
     

    QAQ这道题看起来并没有快很多,反而慢了,自己也不太清楚,先占坑吧,

    如有错误和不足欢迎指出,谢谢大家~

    参考:

    《数据结构 c语言版》严蔚敏

    https://www.cnblogs.com/yspworld/p/4546098.html
  • 相关阅读:
    git整理
    oracle中utl_raw
    mysqltest语法整理
    oracle存储过程中拼接字符串及转义逗号
    oracle存储过程中循环游标,变量的引用
    oracle触发器
    oracle序列相关
    编译1
    面向对象的脚本语言的类的实现
    词法分析器
  • 原文地址:https://www.cnblogs.com/LjwCarrot/p/9486538.html
Copyright © 2020-2023  润新知