图论-最小生成树

首先来了解一下定义:

  •  生成树的定义:在一个任意连通图G中,如果取它的全部顶点和一部分边构成一个子图G‘ , 即:V(G’)=V(G)和若同时满足:

                                      1.边集E(G‘)中的所有边能够使全部顶点连通      2.不形成任何回路         则称子图G’是原图G的一颗生成树。

  • 最小生成树的定义:   所有生成树中权值和最小的生成树即为最小生成树。

 

根据定义,要产生最小生成树,首先要保证图是连通的。怎样判断呢?有三种方法:BFS,DFS,和并查集。  这里甩出一个非常清晰的文章供参考:https://www.cnblogs.com/Roni-i/p/8456657.html

/***************************************************************************************************************************************************************************************/

好啦,判断出连通图后,我们就要求最小生成树了!我学习了两种算法:Prim 和 Kruskal.

prim算法

prim是一种贪心算法:

证明:https://blog.csdn.net/abcef31415926/article/details/52684829     可用反证法证明。

下面给出洛谷P3366计算最小生成树的AC代码——(用了vector数组建图,BFS判断连通图,和prim算法)

#include <bits/stdc++.h>
//洛谷裸题,先用dfs判断连通图,然后用prim
using namespace std;
#define INF 0x3f3f3f3f
const int maxn=2e5+10;
vector<int> G[maxn],W[maxn];
int n,m; 
int vis[5010];
int dis[5010];
//dfs判断是不是连通图 
void dfs(int x){
    vis[x]=1;
    int num=G[x].size();
    for(int i=0;i<num;i++){
        if(!vis[G[x][i]]) dfs(G[x][i]);
    }

}
long long ans=0;
int prim(){
//    for(int i=0;i<5010;i++) vis[i]=0;
//    for(int i=0;i<5010;i++) dis[i]=INF;   可以用memset来初始化
    memset(vis,0,sizeof(vis));
    memset(dis,INF,sizeof(dis)); 
    int num=G[1].size();
    for(int i=0;i<num;i++){    //初始化:和1相邻的点到 1的距离是点集B到A的距离最小值 
        dis[G[1][i]]=min(dis[G[1][i]],W[1][i]);
    }
    vis[1]=1;
    for(int i=1;i<n;i++){      //执行n-1次,添加n-1条边 
        int u=0;  
        for(int j=1;j<=n;j++){
            if(!vis[j] && dis[u]>dis[j]){
                u=j;
            }
        }
        vis[u]=1;
        ans+=dis[u];
        num=G[u].size();
        for(int j=0;j<num;j++){    //更新加入点后的dis 
            dis[G[u][j]]=min(dis[G[u][j]],W[u][j]);
        }
    }
    return ans;
}

int main(int argc, char** argv) {
    int x,y,z;
    cin>>n>>m;
    for(int i=0;i<m;i++){
        cin>>x>>y>>z;
        G[x].push_back(y);  //一定要注意!!无向图两种方向push进去! 
        W[x].push_back(z);
        G[y].push_back(x);
        W[y].push_back(z); 
    }
    dfs(1);
    for(int i=1;i<=n;i++){
        if(!vis[i]){
            cout<<"orz"<<endl;
            return 0;
        }
    }
    
    ans=prim();
    cout<<ans<<endl;
     
    return 0;
}
view code

prim算法的复杂度是 (n2+m).可以用优先队列来优化,因为我们发现,每次为了找出距离点集A最小的边,都要遍历所有的点,并且A加入新的点后,还要更新所有与该点相连的点到A的距离。如果用优先队列维护,每次push进去后,只需要logm的时间维护这个队列(最坏情况是所有的边都push进去),pop只需要O(1)的时间。添加n-1个点,所以复杂度降为了(nlogm)

int head[MAXN],to[MAXN],nxt[MAXN],w[MAXN],o;  
bool vis[MAXN];  
int dis[MAXN];  
void add_edge(int a,int b,int c){  
    nxt[o]=head[a];  
    to[o]=b;  
    w[o]=c;  
    head[a]=o++;  
    nxt[o]=head[b];  
    to[o]=a;  
    w[o]=c;  
    head[b]=o++;  
}  

typedef pair<int,int> PII;  
int prim(int n)  
{  
    memset(vis,0,sizeof(vis));  
    priority_queue<PII,vector<PII>,greater<PII> > Q;  
    int ret=0;  
    vis[1]=1;  
    for(int i=head[1];~i;i=nxt[i]) Q.push({w[i],to[i]});  
    while(!Q.empty()){  
        int val=Q.top().first;  
        int x=Q.top().second;  
        Q.pop();  
        if(vis[x]) continue;  
        vis[x]=1;  
        ret+=val;  
        for(int i=head[x];~i;i=nxt[i])  
            if(!vis[to[i]])  
                Q.push({w[i],to[i]});  
    }  
    return ret;  
}  
View Code

Kruskal算法

Kruskal算法依然是贪心将图G中的边按权值从小到大的顺序依次选取每一条边,对于每一条边: 

  若选取的一条边使生成树不产生回路,把它并入生成树的边集中;

  若选取的一条边使生成树产生回路,舍弃。

重复进行直至最小生成树包含n-1条边。时间复杂度:(mlogm)

证明:https://www.cnblogs.com/tahitian-fang/p/5751298.html

下面依然是洛谷P3366的AC代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
struct edge{   //kruskal算法和这种存图方式一般是配套的
    int u,v,w;
    bool operator <(const edge &t)const{
        return w<t.w;
    }
}edges[maxn];
int n,m;
long long ans=0;
int fa[5010];

int find(int x){
    return x==fa[x]?x:fa[x]=find(fa[x]); 
}

void merge(int x,int y){   //注意!!这里应该是find(x),而不是比较fa[x]和fa[y]!
    x=find(x);
    y=find(y);
    if(x==y) return ;
    else fa[x]=y;
}
bool liantong(){     //用并查集判断是否连通
    for(int i=1;i<=n;i++) fa[i]=i;   
    for(int i=0;i<m;i++){
        merge(edges[i].u,edges[i].v);
    }
    int cnt=0;
    for(int i=1;i<=n;i++){
        if(fa[i]==i) cnt++;
    }
    if(cnt==1) return true;
    else return false;
}
long long kruskal(){
    int cnt=0;
    ans=0;
    for(int i=1;i<=n;i++) fa[i]=i;   //初始化
    sort(edges,edges+m);
    for(int i=0;i<m;i++){
        int a=find(edges[i].u),b=find(edges[i].v);  //并查集判断是否在一颗树上
        if(a==b){
            continue;
        }
        else{
            merge(a,b);
            ans+=edges[i].w;
            cnt++;
        }
        if(cnt==n-1) return ans;
    }
    
}
int main(int argc, char** argv) {
//    freopen("testdata.txt","r",stdin);
    cin>>n>>m;
    int x,y,z;
    for(int i=0;i<m;i++){
        cin>>x>>y>>z;
        edges[i].u=x;
        edges[i].v=y;
        edges[i].w=z;
    }
    if(!liantong()){
        cout<<"orz"<<endl;
        return 0;
    } 
    else{
        ans=kruskal();
        cout<<ans<<endl;
    }
    return 0;
}
View Code

 

posted @ 2019-07-21 22:05  go_ooooon  阅读(274)  评论(0编辑  收藏  举报