最小生成树--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\n",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\n",ans);
    }
    return 0;
}
 

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

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

参考:

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

https://www.cnblogs.com/yspworld/p/4546098.html

转载于:https://www.cnblogs.com/LjwCarrot/p/9486538.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值