3.29 最小生成树算法

最小生成树概念

参考:什么是最小生成树?

Minimum Spanning Tree

何为生成树?

生成树是指一个联通图的极小的连通子图,它包含了图中的所有n个顶点,并只有n-1条边(构成一棵树)
3.29 最小生成树算法_第1张图片
生成树的一些性质:

  • 一个连通图可以有多种生成树(如上图所示)
  • 生成树中不存在环
  • 任何一个边的加入都会使得生成树中出现环

何为最小生成树?

最小生成树是针对一个带权图来说的,就是指原图中能构成的所有生成树中,边权和最小的一颗生成树

对于下面的连通图:
3.29 最小生成树算法_第2张图片
可以有多种选取边(即构成了生成树的情况):
3.29 最小生成树算法_第3张图片
我们称权值和最小的树为最小生成树

最小生成树有什么意义呢?

接上图,如果我们需要在某城市之间修建道路,点与点之间的加权边就是两城市道路的维修费,如果我们想找到一种将所有城市都连通起来并且使得道路修建费用最小的一种方案,那么就要选择使用最小生成树。

最小生成树构建方法

两种方法都是基于贪心
有一点,其实我不太明白。“Kruskal算法维护的是边,所以不需要对于无向图存储两次边。Prim维护的是集合之间点的关系,因此需要对无向图存储两次边

克鲁斯卡尔算法(Kruskal)

算法思想

将所有的带权边按照权值从小到大排列,从最小的开始选择,如果加入该边后不会产生环,则加入,反之则不加入。直到已经选择了n-1条边。
这里不会产生环的判定比较困难,因此可以换一下想法:
就是只要选择的边的两端点不在同一连通块中即可。如下所示
在这里插入图片描述
使用并查集就可以检查联通问题了


#include
#include

using namespace std;

const int N=2*1e5+10;

int n,m;
int p[N];
int ans=0;//最终权值
int res=0;//已经放入的边数

struct nn{
    int a;
    int b;
    int w;
}node[N];

int cmp(nn a, nn b){
    return a.w<b.w;
}

int find(int x){
    if(p[x]!=x) p[x]=find(p[x]);
    return p[x];
}


int kruskal(){
    
    for(int i=1;i<=n;i++)
        p[i]=i;
    
    for(int i=1;i<=m;i++){
        //遍历m条边
        int a=node[i].a, b=node[i].b, w=node[i].w;
        a=find(a); b=find(b);
        if(a!=b){
            //如果不在一个联通图中
            p[a]=b;//连接两个点
            ans+=w;
            res++;
        }
        if(res==n-1){
            return res;
        }
    }
    return res;
}

int main(){
    
    cin>>n>>m;
    
    for(int i=1;i<=m;i++){
        cin>>node[i].a>>node[i].b>>node[i].w;
    }
    
    sort(node+1,node+1+m,cmp);
    
    if(kruskal()==n-1)
        cout<<ans<<endl;
    else
        cout<<"impossible"<<endl;
    
    return 0;
}

普里姆算法(Prim)

和kruskal算法(遍历边)不同的是,prim算法是对点进行遍历

算法思路

对于每个点,每次找到一个集合外的,距离该集合最近的点t。(该集合是一个已经确定了的连通块)
用点t更新集合外点到集合的距离,就是找集合外和t有连接的点,如果更小就更新,最后将点t放入集合中。(这里描述的不是很清楚)

st[j]==0&&(t==-1||dist[t]>dist[j]) 注意这里的判断,首先判断该点是否已经进入集合,然后再与上(t==-1和大小的或运算)


#include
#include
#include

using namespace std;

const int N=510,INF=0x3f3f3f3f;
int n,m;

int g[N][N];//用于存储边
int dist[N];//点距离集合的距离
int st[N];//表示是否已经进入集合
int ans=0;

int prim(){
    
    dist[1]=0;//强制一开始选择的点就是点1
    
    for(int i=1;i<=n;i++){//外层循环是指循环n次以遍历所有n个点
        
        int t=-1;
        for(int j=1;j<=n;j++){
            if(st[j]==0&&(t==-1||dist[t]>dist[j])){
                t=j;
            }  
        }
        //找到最短的dist,下标为t
        
        if(dist[t]==INF)    return INF;//说明没有点能够到达当前的集合
        
        ans+=dist[t];
        
        for(int j=1;j<=n;j++){
            if(st[j]==0&&j!=t){
                //更新所有集合外的点到该集合的距离
                dist[j]=min(dist[j],g[t][j]);
            }
        }
        
        st[t]=1;//将点加入集合
        
    }
    
    
}

int main(){
    
    cin>>n>>m;
    
    memset(g,0x3f,sizeof g);
    
    for(int i=1;i<=m;i++){
        int a,b,c;
        cin>>a>>b>>c;
        g[a][b]=g[b][a]=min(g[a][b],c);
    }
    
    memset(dist,0x3f,sizeof dist);
    
    if(prim()==INF)
        cout<<"impossible"<<endl;
        
    else
        cout<<ans<<endl;
    
    return 0;
}

你可能感兴趣的:(机试备考,算法,图论,c++)