算法竞赛模板 最小生成树

①克鲁斯卡尔算法(Kruskal)

n是顶点数,m是边数;

cnt是已连的边数,如果已连的边数=总点数-1,即可跳出。

#include<bits/stdc++.h>
#define MAX 1005
using namespace std; 
int p[MAX],n,m;
struct edge{
    int x,y,w;
}a[MAX]; 
int find(int r)
{
    if(p[r]!=r)
        p[r]=find(p[r]);
    return p[r];
}
void join(int x,int y)
{
    int fx=find(x),fy=find(y);
    if(fx!=fy)
        p[fx]=fy;
}
void init()
{
    for(int i=0;i<=MAX;i++)
        p[i]=i;
}
bool cmp(edge a,edge b)
{
    return a.w<b.w;
}
int kruskal()
{
    sort(a,a+m,cmp);
    int cnt=0,cost=0,i;
    for(i=0;i<m;i++)
    {
        int fx=find(a[i].x),fy=find(a[i].y);
        if(fx!=fy)
        {
            p[fx]=fy;
            cost+=a[i].w;
            cnt++;
        } 
        if(cnt==n-1)break;
    }
    return cost;
}
int main()
{
    int d,i;
    while(cin>>n>>m)
    {
        init();
        for(i=0;i<m;i++)
        {
            scanf("%d%d%d%d",&a[i].x,&a[i].y,&a[i].w,&d);
            if(d==1)//将已有的路连起来 
                join(a[i].x,a[i].y);
        }
        cout<<kruskal()<<endl;    
    }
    return 0;
}

 

②普里姆算法(Prim)

(1) n是顶点数,m是边数;

(2) edge[i][j]表示边e=(i,j)的权值,不存在的情况下设为INF,所以要记得提前做赋值操作;

(3) mincost[i]数组表示已访问点的集合每个未访问点 i 的最小权值;

(4) vis[i]表示顶点 i 是否已访问

 

<1> 基础prim算法

#include<bits/stdc++.h>
#define MAX 1005
#define INF 0x3f3f3f3f
using namespace std;
int n,m,edge[MAX][MAX],mincost[MAX],vis[MAX];
void init()
{
    int u,v,w,i;
    memset(edge,INF,sizeof(edge));
    for(i=1;i<=m;i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        edge[u][v]=edge[v][u]=w;
    }
    for(i=1;i<=n;i++)
    {
        vis[i]=0;
        mincost[i]=INF;
    }
    mincost[1]=0;
}
int prim()
{
    int i,v,cost=0;
    init();
    while(1)
    {
        v=-1;
        for(i=1;i<=n;i++)
            if(!vis[i]&&(v==-1||mincost[i]<mincost[v]))
                v=i;
        if(v==-1)
            break;
        vis[v]=1;
        cost+=mincost[v];
        for(i=1;i<=n;i++)
            mincost[i]=min(mincost[i],edge[i][v]);
    }
    return cost;
}
int main()
{
    cin>>n>>m;
    cout<<prim()<<endl;
    return 0;
}

 

<2> prim之记录并输出每次加入的边

#include<bits/stdc++.h>
#define MAX 1005
#define INF 0x3f3f3f3f
using namespace std;
int n,m,edge[MAX][MAX],mincost[MAX],vis[MAX];
void init()
{
    int u,v,w,i;
    memset(edge,INF,sizeof(edge));
    for(i=1;i<=m;i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        edge[u][v]=edge[v][u]=w;
    }
    for(i=1;i<=n;i++)
    {
        vis[i]=0;
        mincost[i]=INF;
    }
    mincost[1]=0;
}
void prim()
{
    int i,v,start,end,sum=0,minn;
    init();
    while(1)
    {
        v=-1;
        for(i=1;i<=n;i++)
            if(!vis[i]&&(v==-1||mincost[i]<mincost[v]))
                v=i;

        if(v==-1)
            break;

        vis[v]=1,sum++;
        minn=INF;
        for(i=1;i<=n;i++)
        {
            mincost[i]=min(mincost[i],edge[i][v]);
            if(minn>mincost[i]&&!vis[i])
            {
                minn=mincost[i];
                end=i;
            }
        }
        if(sum==n)
            continue;

        minn=INF;
        for(i=n;i>=1;i--)
        {
            if(minn>=edge[i][end]&&vis[i])
            {
                minn=edge[i][end];
                start=i;
            }
        }
        printf("%d %d %d\n",start,end,mincost[end]);
    }
}
int main()
{
    cin>>n>>m;
    prim();
    return 0;
}

 

#include<bits/stdc++.h>
#define MAX 1005
#define INF 0x3f3f3f3f
using namespace std;
int n,m,edge[MAX][MAX],mincost[MAX],vis[MAX];
void init()
{
    int u,v,w,i;
    memset(edge,INF,sizeof(edge));
    for(i=1;i<=m;i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        edge[u][v]=edge[v][u]=w;
    }
    for(i=1;i<=n;i++)
    {
        vis[i]=0;
        mincost[i]=INF;
    }
    mincost[1]=0;   //必须先确保下标的这个点1是存在的!
}
int prim()
{
    int i,v,cost=0;
    init();
    while(1)
    {
        /*--------------------循环A---------------------*/
        /*
            PS:点v是距已访问点集合最近的未访问点

            1、若点i是进循环A以来,第一个未被访问过的点,则无条件保存到点v
            2、若点i不是第一个未被访问过的点,但点i距离已访问点集合更近,则保存到点v
        */
        v=-1;
        for(i=1;i<=n;i++)
            if(!vis[i]&&(v==-1||mincost[i]<mincost[v]))
                v=i;

        /*--------------------循环A---------------------*/


        //如果所有点都被访问过了,v还是-1,则会跳出这个while循环
        if(v==-1)
            break;

        //将访问到的最近点v标记为已访问状态
        vis[v]=1;

        //总距离增加
        cost+=mincost[v];


        /*--------------------循环B---------------------*/
        /*
            PS:mincost[i]是已访问点集合到每个未访问点i的最小权值

            1、若新增的点v距离某个未访问点i相较于之前更近,则将 已访问点集合到i的距离 更新为edge[v][i]
            2、否则保持原样
        */
        for(i=1;i<=n;i++)
            mincost[i]=min(mincost[i],edge[v][i]);

        /*--------------------循环B---------------------*/
    }
    return cost;
}
int main()
{
    cin>>n>>m;
    cout<<prim()<<endl;
    return 0;
}
prim算法详解!不理解就ClickHere!!!!

③ 总结

Kruskal在所有边中不断寻找最小的边,Prim在U和V两个集合之间寻找权值最小的连接。

它们的共同点在于:构造过程中都不能形成环!

(1) 时间上:

  Prim适合稠密图,复杂度为O(n*n),因此通常使用邻接矩阵储存;

  Kruskal适合稀疏图,多用邻接表储存,复杂度为O(e*loge)。

(2) 空间上:

  Prim适合边较多的情况;

  Kruskal适合点较多的情况。

posted @ 2018-03-17 10:14  真想不出名字了  阅读(261)  评论(0编辑  收藏  举报