最小生成树详解

注:本文算法使用链式前向星数据结构实现。学习链接:链式前向星-学习笔记

一、Prim算法

普通prim算法模板:

//用前向星录数据的时候记得把head初始化为-1 
fill(dist,dist+LEN,MAX);
memset(vis,0,sizeof vis);
int ans=0;
dist[1]=0;        //如果题目要求输出最小生成树,就把题目要求的源点s的dist设为0 
while(1){        //如果题目要求判断最小生成树是否能覆盖所有边,这个循环条件应该是i=n;while(n--)循环n次。 
    int u=-1,d=MAX;
    for(i=1;i<=N;i++){
        if(!vis[i] && dist[i]<d){
            u=i;
            d=dist[i];
        }
    }
    if(u<0) break;    //如果题目要求判断最小生成树是否能覆盖所有边,出现这样的情况说明不能覆盖所有边。 
    vis[u]=1;
    ans+=dist[u];
    for(i=head[u];~i;i=mp[i].next){    //用前向星遍历u点所有的后继。i是各个后继点在mp的下标,mp[to]是u的各个后继点 
        int to=mp[i].to;
        if(!vis[to] && mp[i].w<dist[to]){//如果这个点没有被访问过、并且u->v的路径比点集S到v的路径要短,则更新。 
            dist[to]=mp[i].w;
        }
    }
}
O("%d\n",ans);

堆优化的prim算法:

堆结构:

struct cmp{
    bool operator () (int a,int b){
        return dist[a]>dist[b];
    }
}; 
priority_queue<int,vector<int>,cmp> pq;

算法代码:

int ans=0;
dist[1]=0;
pq.push(1);
while(!pq.empty()){
    int u=pq.top();
    pq.pop();
    if(vis[u]) continue;
    vis[u]=1;
    ans+=dist[u];
    for(i=head[u];~i;i=mp[i].next){
        int to=mp[i].to;
        if(!vis[to] && mp[i].w<dist[to]){
            dist[to]=mp[i].w;
            pq.push(to);
        }
    }
}
O("%d\n",ans);

 

二、Kruskal算法

1.建立边表数据结构

typedef struct edge{
    int u,v,w;
    edge(int u=0,int v=0,int w=0):u(u),v(v),w(w)
    {
    }
    bool operator < (const edge& obj) const
    {
        return w<obj.w;
    }
}edge;
edge mp[LEN*LEN];

2.编写并查集模板(以下代码没有写合并的Union操作。这个操作在主代码执行的时候已经实现)

int fa[LEN];
int init(){
    int i;
    FF(i,LEN) fa[i]=i;
}
int findFa(int x){
    if(x==fa[x]) return x;
    int r=x;
    while(r!=fa[r]){
        r=fa[r];
    }
    int t=x;
    while(x!=fa[x]){
        t=fa[x];
        fa[x]=r;
        x=t;
    }
    return r;
}

3.编写主代码

sort(mp,mp+cnt);
FF(i,cnt){
    int fa_u=findFa(mp[i].u);
    int fa_v=findFa(mp[i].v);
    if(fa_u!=fa_v){
        ans+=mp[i].w;
        fa[fa_u]=fa_v;
        edge_cnt++;
        if(edge_cnt>=N-1) break;
    }
}
O("%d\n",ans);

注意:

①边表的范围要开大,因为边的数目可能是顶点数目的平方(准确说,有向图边树E=N*(N-1) )

②Prim算法在录边的数据的时候,因为是无向图,一条边要录成两条。Kruskal就没有这种必要了。

③各种初始化代码(比如并查集的init() )要注意加上。

 

打个OJ测试一下吧!

OJ链接:还是畅通工程

AC代码:

#include <stdio.h>
#include <memory.h>
#include <math.h>
#include <string>
#include <vector>
#include <set>
#include <stack>
#include <queue>
#include <algorithm>
#include <map>

#define I scanf
#define OL puts
#define O printf
#define F(a,b,c) for(a=b;a<c;a++)
#define FF(a,b) for(a=0;a<b;a++)
#define FG(a,b) for(a=b-1;a>=0;a--)
#define LEN 1010
#define MAX (1<<30)-1
#define V vector<int>

using namespace std;

int N;
int fa[LEN];
int init(){
    int i;
    FF(i,LEN) fa[i]=i;
}
int findFa(int x){
    if(x==fa[x]) return x;
    int r=x;
    while(r!=fa[r]){
        r=fa[r];
    }
    int t=x;
    while(x!=fa[x]){
        t=fa[x];
        fa[x]=r;
        x=t;
    }
    return r;
}

typedef struct edge{
    int u,v,w;
    edge(int u=0,int v=0,int w=0):u(u),v(v),w(w)
    {
    }
    bool operator < (const edge& obj) const
    {
        return w<obj.w;
    }
}edge;
edge mp[LEN*LEN];
int cnt=0;

int main(){
//    freopen("还是畅通工程.txt","r",stdin);
    int i,j,u,v,w;
    while(scanf("%d",&N),N){
        init();
        cnt=0;
        int ans=0;
        int edge_cnt=0;
        i=(N*(N-1))/2;
        while(i--){
            I("%d%d%d",&u,&v,&w);
            mp[cnt++]=edge(u,v,w);
//            mp[cnt++]=edge(v,u,w);
        }
        sort(mp,mp+cnt);
        FF(i,cnt){
            int fa_u=findFa(mp[i].u);
            int fa_v=findFa(mp[i].v);
            if(fa_u!=fa_v){
                ans+=mp[i].w;
                fa[fa_u]=fa_v;
                edge_cnt++;
                if(edge_cnt>=N-1) break;
            }
        }
        O("%d\n",ans);
    }
    
    return 0;
}
View Code

 

posted @ 2018-03-12 15:05  TQCAI  阅读(178)  评论(0编辑  收藏  举报