最小生成树基础与习题

目录

图的几个基础概念

Kruskal算法

算法思想

                  模板

Prim算法

算法思想

模板

例题

A.POJ-1251 Jungle Roads

B.POJ-1287 Networking

C:POJ-2031 Building a Space Station

D:ZOJ-1586 QS Network

E:POJ-2349 Arctic Network

F:POJ-1751 Highways


图的几个基础概念

连通图:在无向图中,若任意两个顶点vivi与vjvj都有路径相通,则称该无向图为连通图。
强连通图:在有向图中,若任意两个顶点vivi与vjvj都有路径相通,则称该有向图为强连通图。
连通网:在连通图中,若图的边具有一定的意义,每一条边都对应着一个数,称为权;权代表着连接连个顶点的代价,称这种连通图叫做连通网。
生成树:一个连通图的生成树是指一个连通子图,它含有图中全部n个顶点,但只有足以构成一棵树的n-1条边。一颗有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环。
最小生成树:在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树。 

Kruskal算法(参考最小生成树

算法思想

此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。 
1. 把图中的所有边按代价从小到大排序; 
2. 把图中的n个顶点看成独立的n棵树组成的森林; 
3. 按权值从小到大选择边,所选的边连接的两个顶点ui,vi,应属于两颗不同的树,则成为最小生成树的一条边,并将这两颗树合并作为一颗树。 
4. 重复(3),直到所有顶点都在一颗树内或者有n-1条边为止。

模板

struct node
{
    int s,e,w;
}a[500];
int fa[30];
int n,m;
int Find(int x)
{
    if(x==fa[x])
        return x;
    return fa[x]=Find(fa[x]);
}
bool cmp(node a,node b)
{
    return a.w<b.w;
}
int Kruskal()
{
    sort(a,a+m,cmp);
    int ans=0;
    for(int i=0;i<n;i++)
            fa[i]=i;
    for(int i=0;i<m;i++)
    {
        int fx=Find(a[i].s);
        int fy=Find(a[i].e);
        if(fx!=fy)
        {
            ans+=a[i].w;
            fa[fx]=fy;
        }
    }
    return ans;
}

Prim算法

算法思想

Prim算法的每一步都会为一棵生长中的树添加一条边。一开始这棵树只有一个顶点,然后会向它添加V-1条边,每次总是将下一条连接树中的顶点与不在树中的顶点且权重最小的边加入树中。

模板

int Prime()
{
    for(int i=0; i<n; i++)
    {
        dis[i]=a[0][i];
        vis[i]=false;
    }
    dis[0]=0;
    vis[0]=true;
    int ans=0;
    for(int i=0; i<n-1; i++)
    {
        int p=-1;
        int minn=INF;
        for(int j=0; j<n; j++)
        {
            if(!vis[j]&&dis[j]<minn)
                minn=dis[p=j];
        }
        ans+=minn;
        vis[p]=true;
        for(int j=1; j<n; j++)
        {
            if(!vis[j]&&dis[j]>a[p][j])
                dis[j]=a[p][j];
        }
    }
    return ans;
}

例题

A.POJ-1251 Jungle Roads:一道简单的模板题,AC代码:

#include <iostream>
#include <algorithm>
using namespace std;
const int INF=0x3f3f3f3f;
struct node
{
    int s,e,w;
}a[500];
int fa[30];
int n,m;
int Find(int x)
{
    if(x==fa[x])
        return x;
    return fa[x]=Find(fa[x]);
}
bool cmp(node a,node b)
{
    return a.w<b.w;
}
int Kruskal()
{
    sort(a,a+m,cmp);
    int ans=0;
    for(int i=0;i<n;i++)
            fa[i]=i;
    for(int i=0;i<m;i++)
    {
        int fx=Find(a[i].s);
        int fy=Find(a[i].e);
        if(fx!=fy)
        {
            ans+=a[i].w;
            fa[fx]=fy;
        }
    }
    return ans;
}
int main()
{
    while(cin>>n,n)
    {
        int x,y;
        char ch1,ch2;
        
        m=0;
        for(int i=1;i<n;i++)
        {
            cin>>ch1>>x;
            while(x--)
            {
                cin>>ch2>>y;
                a[m].s=ch1-'A';
                a[m].e=ch2-'A';
                a[m++].w=y;
            }
        }
        cout<<Kruskal()<<endl;
    }
    return 0;
}

B.POJ-1287 Networking:同样的模板题,AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 50+10;
int pre[MAXN];
struct node{
	int u,v,length;
}p[300000];
int cmp(node a,node b){
	return a.length<b.length;
}
 
void init(int n){
	for(int i=1;i<=n;i++){
		pre[i]=i;
	}
}
int find(int x){
	if(x==pre[x]) return x;
	else return pre[x]=find(pre[x]);
}
 
int main(){
	int m,n;
	while(~scanf("%d",&n) &&n){
		scanf("%d",&m);
		init(n);
		memset(p,0,sizeof(p));
		for(int i=0;i<m;i++){
			scanf("%d %d %d",&p[i].u,&p[i].v,&p[i].length);
		}
		sort(p,p+m,cmp);
		int ans=0;
		for(int i=0;i<m;i++){
			int dx=find(p[i].u);
			int dy=find(p[i].v);
			if(dx!=dy){
				ans+=p[i].length;
				pre[dx]=dy;
			}
		}
		printf("%d
",ans);
	}
	return 0;
}

C:POJ-2031 Building a Space Station:模板题,给出两个解法:

Kruskal解法

/****************************************************
Accepted	248 KB	32 ms	C++	1885 B	2013-07-26 15:47:09
题意:在三维空间中给你 n 个球体的坐标和半径
       如果这些球体间没有相通,则需要你去建立一些通道把所有的球体连接起来。
       表面相切即可认为相通。
算法:最小生成树Kruskal 复杂度 O(E)
思路:经典最小生成树题目
      首先在各球体间建图,然后再按照边从小到大排序
      用并查集查找两点是否属于同一联通分量【即判断这条边的两个球是否相通】
      如果不属于同一联通分量,则连接即可
      由于每次都是找的最短的边,所以最终所求一定是最短距离了。
****************************************************/
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<algorithm>
#include<iostream>
using namespace std;
 
const int maxn = 110;
int n,m;
 
struct Point{
    double x,y,z;
    double r;
}p[maxn];
int f[maxn]; /**父亲*/
 
struct Edge{
    int u,v;
    double w;
}edge[maxn*maxn];
 
bool cmp(Edge L1, Edge L2)
{
    return L1.w < L2.w;
}
 
double dist(Point A, Point B)
{
    return sqrt((A.x-B.x)*(A.x-B.x) + (A.y-B.y)*(A.y-B.y) + (A.z-B.z)*(A.z-B.z));
}
 
int find(int x) /** 并查集find*/
{
    return x == f[x] ? x : f[x] = find(f[x]);
}
 
double Kruskal() /** 传说中的 Kruskal 算法, 学并查集时居然没有看到Orz*/
{
    double ans = 0;
    for(int i = 0; i < m; i++) /*排序后遍历的边一定是从小到大的*/
    {
        int u = find(edge[i].u); /**找祖宗*/
        int v = find(edge[i].v);
 
        if(u == v) continue; /**祖宗相同, 属于同一连通分量*/
        else  /**属于不同联通分量, 合并*/
        {
           ans += edge[i].w;
           f[u] = v;
        }
    }
    return ans;
}
int main()
{
    while(scanf("%d", &n) != EOF)
    {
        if(n == 0) break;
 
        for(int i  = 0; i < n; i++)
        {
            scanf("%lf%lf%lf%lf", &p[i].x, &p[i].y, &p[i].z, &p[i].r);
            f[i] = i; /** 初始化并查集,自己是自己的祖宗*/
        }
 
        m = 0; /** 初始化边的数量*/
        for(int i = 0; i < n-1; i++)
        {
            for(int j = i+1; j < n; j++)
            {
                edge[m].u = i;
                edge[m].v = j;
                edge[m].w = max(0.0, dist(p[i],p[j])-p[i].r-p[j].r); /**如果两个圆相交,则定义距离为 0 */
                m++;
            }
        }
        sort(edge,edge+m,cmp); /** 把边按照长度从小到大排序 */
 
        double ans = Kruskal();
        printf("%.3lf
", ans);
    }
    return 0;
}

Prim解法 

#include<stdio.h>
#include<math.h>
int m,n,book[110];
double sum,e[110][110],dis[110];
double inf = 99999999.0;
void Prim()
{
	int i,j,k;
	double min;
	for(i = 1; i <= n; i++)
	{
		dis[i] = e[1][i];
		book[i] = 0;
	}
	dis[1] = 0;
	book[1] = 1;
	for(i = 1; i < n; i ++)
	{
		min = inf;
		for(j = 1; j <= n; j ++)
			if(book[j] == 0 && dis[j] < min)
			{
				min = dis[j];
				k = j;
			}
		sum += min;
		book[k] = 1;
		for(j = 1; j <= n; j ++)
			if(book[j] == 0 && dis[j] > e[k][j])
				dis[j] = e[k][j];
	}
}
int main()
{
	int i,j;
	double d,x[110],y[110],z[110],r[110];
	while(scanf("%d",&n), n != 0)
	{
		sum = 0.0;
		for(i = 1; i <= n; i ++)
			for(j = 1; j <= n; j ++)
				if(i == j)
					e[i][j] = 0.0;
				else
					e[i][j] = inf;
		for(i = 1; i <= n; i ++)
			scanf("%lf%lf%lf%lf",&x[i],&y[i],&z[i],&r[i]);
		for(i = 1; i <= n; i ++)
			for(j = 1; j <= n; j ++)
			{
				d = sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j])+(z[i]-z[j])*(z[i]-z[j]));
				if(d > r[i] + r[j])
					e[i][j] = e[j][i] = d-r[i]-r[j];
				else
					e[i][j] = e[j][i] = 0;
			}
		Prim();
		printf("%.3lf
",sum);
	}
	return 0;
}

D:ZOJ-1586 QS Network:有一道模拟题,AC代码:

#include<bits/stdc++.h>
using namespace std;
int t,m,n,sum;
const int maxn = 1005;
const int inf = 0x3f3f3f3f;
int a[maxn][maxn];
int vis[maxn];
int length[maxn];
int spend[maxn];
void prim()
{
    for(int i=1;i<=n;i++)
    {
        length[i]=a[1][i];
        vis[i]=0;
    }
    vis[1]=1;
    for(int i=1;i<n;i++)
    {
        int mi=inf;
        int u;
        for(int j=1;j<=n;j++)
        {
            if(vis[j]==0&&mi>length[j])
            {
                mi=length[j];
                u=j;
            }
        }
        vis[u]=1;
        sum+=length[u];
        for(int j=1;j<=n;j++)
        {
            if(vis[j]==0&&length[j]>a[u][j])
            {
                length[j]=a[u][j];
            }
        }
       // if(i!=1)sum+=spend[u]+spend[path[u]];
 
    }
}
int main()
{
    cin>>t;
    while(t--)
    {
        sum=0;
        cin>>n;
        for(int i=1;i<=n;i++)cin>>spend[i];
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
            {
                cin>>a[i][j];
                if(i!=j)a[i][j]+=spend[i]+spend[j];
            }
        prim();
        cout<<sum<<endl;
    }
}

E:POJ-2349 Arctic Network:有 m 个哨所需要m-1条边连接,s颗卫星可以代替s-1条边,用卫星代替最长的边,即dis[m-1-s+1]为剩下最大的D,AC代码:

#include"stdio.h"
#include"math.h"
#include"string.h"
#include"queue"
#include"algorithm"
using namespace std;
#define N 505
const double inf=1e12;
double g[N][N],dis[N];
bool mark[N];
int n,m;
struct node
{
    double x,y;
}e[N];
double fun(int i,int j)
{
    double x,y;
    x=e[i].x-e[j].x;
    y=e[i].y-e[j].y;
    return sqrt(x*x+y*y);
}
void prim(int s)
{
    int i,j,u;
	double min;
    memset(mark,0,sizeof(mark));
    mark[s]=1;
    for(i=0;i<m;i++)
    {
        dis[i]=g[s][i];
    }
	dis[s]=0;
    for(j=1;j<m;j++)
    {
        u=s;
        min=inf;
        for(i=0;i<m;i++)
        {
            if(!mark[i]&&dis[i]<min)
            {
                min=dis[i];
                u=i;
            }
        }
        mark[u]=1;
        for(i=0;i<m;i++)
        {
            if(!mark[i]&&dis[i]>g[u][i])
                dis[i]=g[u][i];
        }
    }
    sort(dis,dis+m);        //从小到大排序
    printf("%.2f
",dis[m-n]);
}
int main()
{
    int T,i,j;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        for(i=0;i<m;i++)
        {
            scanf("%lf%lf",&e[i].x,&e[i].y);
        }
        for(i=0;i<m;i++)
        {
            for(j=0;j<i;j++)
            {
                g[i][j]=g[j][i]=fun(i,j);
            }
            g[i][i]=inf;       //注意初始化
        }
        prim(0);
    }
    return 0;
}

F:POJ-1751 Highways:最小生成树输出带路径的问题,两种解法的AC代码:

Prim解法

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
#define sf scanf
#define pf printf
#define MAXN 1100
#define INF 1<<29
#define clr(x) memset(x,0,sizeof(x))
int map[MAXN][MAXN];
int path[MAXN];
int dis[MAXN];
int vis[MAXN];
int x[MAXN],y[MAXN];
int n,m;
void Prim(){
	clr(x);
	for(int i=1;i<=n;i++){
		dis[i]=map[1][i];
		path[i]=1;   //此处为与不带路径问题的不同处(多出来的部分)
	}
	dis[1]=0;
	vis[1]=1;
	for(int i=1;i<=n;i++){
		int k=0;
		int min=INF;
		for(int j=1;j<=n;j++){
			if(!vis[j]&&dis[j]<min){
				min=dis[j];
				k=j;
			}
		}
		vis[k]=1;
		if(map[path[k]][k]!=0) pf("%d %d
",k,path[k]);  //此处为与不带路径问题的不同处(多出来的部分)
		for(int j=1;j<=n;j++){
			if(!vis[j]&&dis[j]>map[k][j]){
				dis[j]=map[k][j];
				path[j]=k;     //此处为与不带路径问题的不同处(多出来的部分)
			}
		}
	}
}
int main(){
	sf("%d",&n);
	for(int i=1;i<=n;i++){
		sf("%d%d",&x[i],&y[i]);
		for(int j=1;j<=i;j++){
			map[i][j]=map[j][i]=(x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]);
		}
	}
	sf("%d",&m);
	int a,b;
	for(int i=1;i<=m;i++){
		sf("%d%d",&a,&b);
		map[a][b]=map[b][a]=0;
	}
	Prim();
	return 0;
}

Kruskal解法 

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
using namespace std;
int a[800],b[800],per[800],n,cnt,kase;
struct lu
{
	int a,b;
	double len;
}x[600005];
double dis(int i,int j)
{
	return sqrt((a[i]-a[j])*(a[i]-a[j])*1.0+(b[i]-b[j])*(b[i]-b[j]));//其实完全不必求出距离,只要有个大小顺序就行
}
void init()
{
	for(int i=1;i<=n;++i)
	{
		per[i]=i;
	}
}
bool cmp(lu a,lu b)
{
	return a.len<b.len;
}
int find(int x)
{
	int r=x;
	while(r!=per[r])
	{
		r=per[r];
	}
	int i=x,j;
	while(i!=r)
	{
		j=per[i];per[i]=r;i=j;
	}
	return r;
}
void join(int x,int y)
{
	int fx=find(x),fy=find(y);
	if(fx!=fy)
	{
		per[fy]=fx;
		++cnt;kase=1;
	}
}
void kruskal(int c)
{
	double sum=0;
	for(int i=0;i<c;++i)
	{
		kase=0;
		join(x[i].a,x[i].b);
		if(kase)
		{
			printf("%d %d
",x[i].a,x[i].b);  //此处为与不带路径问题的不同处(多出来的部分)
		}
	}
}
int main()
{
	int i,j,u,v;
	scanf("%d",&n);
	for(i=0;i<n;++i)
	{
		scanf("%d%d",a+i,b+i);
	}
	init();
	int c=cnt=0,m;
	scanf("%d",&m);
	for(i=0;i<m;++i)
	{
		scanf("%d%d",&u,&v);
		join(u,v);
	}
	for(i=0;i<n-1;++i)
	{
		for(j=i+1;j<n;++j)
		{
			x[c].a=i+1;x[c].b=j+1;
			x[c].len=dis(i,j);
			++c;
		}
	}
	sort(x,x+c,cmp);
	kruskal(c);//调用函数
	return 0;
}
原文地址:https://www.cnblogs.com/shmilky/p/14089054.html