acm专题学习之最小生成树(一)最小生成树入门+POJ1251

题意:给你n个点,右n-1条边,每个边都有一个权值,让你求出最小生成树

扩展:最小生成树

最小生成树的概念:在一副加权连通图中,最小生成树包含原图中的所有 n 个结点且权值和最小,并且有保持图连通的最少的边(说的就是不会成环)。

解决最小生成树的算法:prime和kruskal算法

prime算法:

  • 先任意选择一条边(一般直接选择第一条),连接与其相连权值最小的点,然后两个点成为一个集合体。
  • 找这个不在这个集合体里 但是与集合体相连的权值最小的点 与集合体相连,并把该点归入集合体。
  • 重复上一条操作,直到集合体归入了所有的点

kruskal算法:

  • 先选择一条权值最小的边,把这条边相连的两个点归成一个集合。
  • 再找下一个权值最小的边,但是边相连的两个点不能属于同一个集合,把这条边相连的两个点(这里也可以是集合)归成一个集合
  • 重复上一条操作,直到最后只有一个集合体且归入所有的点。

两个的区别:

  1. 集合的个数:prime算法自始自终只有一个集合,而kruskal算法可以有多个集合,所以kruskal算法要用到并查集。
  2. 选取的方式:prime算法先是任意选取,再根据选取已有的基础上选取权值最小且不在集合的点(取点)。kruskal算法则是每次以权值最小的边来选取,可以有多个集合(取边)。

本题代码:

prime算法:

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int maxn = 30;
const int inf = 0x3f3f3f3f;
int mp[maxn][maxn], vis[maxn], ans[maxn];//采用邻接矩阵存道路关系
int n;
void prime()
{
    int sum=0;
	for (int i = 1; i <= n; i++)//先选第一个点为集合
	{
		vis[i] = 0;
		ans[i] = mp[1][i];//存与第一个点成的集合到相连的点的权值
	}
    vis[1]=1;
	for (int i = 1; i <= n; i++)
	{
		int min1 = inf,p=0;
		for (int j = 1; j <= n; j++)
		{
			if (!vis[j] && ans[j] < min1)//找出不属于集合,但是相连权值最小的点
			{
				min1 = ans[j];
				p = j;
			}
		}
		if(min1==inf)break;//如果找不到了,就说明已经找完了,这里找的次数是n,如果没有这个条件很容易把inf算入
		//cout<<min1<<endl;
		vis[p] = 1;//标记已经加入集合了
		sum+=min1;//加入答案中
		for (int j = 1; j <= n; j++)//更新一下于集合相连的点的权值
		{
			if (!vis[j] && mp[p][j] < ans[j])
			{
				ans[j] = mp[p][j];
			}
		}
	}
	cout << sum << endl;
}
int main()
{
	while(cin>>n,n)
	{
		memset(mp, inf, sizeof(mp));
		for (int i = 1; i <n; i++)
		{
			int m;
			char s[10];
			scanf("%s%d", s, &m);
			for(int j=1;j<=m;j++)
			{
				int k;
				char s1[10];
				scanf("%s%d", s1, &k);
				mp[s[0] - 'A' + 1][s1[0] - 'A' + 1] = mp[s1[0] - 'A' + 1][s[0] - 'A' + 1]= k;
			}
		}
		prime();
	}
	return 0;
}

kruskal算法:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 35;
const int inf = 0x3f3f3f3f;
int vis[maxn];//并查集数组
int num;
struct node//用该结构存道路关系
{
    int a,b;
    int val;
}mp[maxn*maxn];
int n;
bool cmp(node a,node b)
{
    return a.val<b.val;
}
int find_father(int x)//并查集寻找祖先
{
    if(vis[x]==0)
        return x;
    else
        return vis[x]=find_father(vis[x]);
}
void Kruskal()
{
    int ans=0,count1=0;
    memset(vis,0,sizeof(vis));
    sort(mp,mp+num,cmp);//因为kruskal是靠从边的最小依次查找的,所以直接排序方便查找
    for(int i=0;i<num;i++)
    {
        int x=find_father(mp[i].a);
        int y=find_father(mp[i].b);
        if(x!=y)//如果两个点所属的集合不同就和并
        {
            vis[x]=y;
            ans+=mp[i].val;
            count1++;
            if(count1==n-1)//当总数满足n-1就可以不用再找了
                break;
        }
    }
    if(count1!=n-1)//如果这个图不是连通的
        cout<<-1<<endl;
    else
        cout<<ans<<endl;
}
int main()
{
	while(cin>>n,n)
	{
	    num=0;
		for (int i = 1; i <n; i++)
		{
			int m;
			char s[10];
			scanf("%s%d", s, &m);
			for(int j=1;j<=m;j++)
			{
				int k;
				char s1[10];
				scanf("%s%d", s1, &k);
				mp[num].a=s[0] - 'A' + 1;
				mp[num].b=s1[0] - 'A' + 1;
				mp[num].val=k;
				num++;
				//mp[s[0] - 'A' + 1][s1[0] - 'A' + 1] = mp[s1[0] - 'A' + 1][s[0] - 'A' + 1]= k;
			}
		}
		Kruskal();
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_40306845/article/details/81540626