[置顶] NYOJ38 布线问题

题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=38

题目分析:
其实就是求图的最小生成树。有两种方法。prim法和kruskal法。prim法只与节点有关,与边无关,所以适合于求边稠密的网的最小生成树。而kruskal算法与边有关,故其适合于求边比较稀疏的网络。
prim算法:
1)初始化set集为随意的一个节点,这里初始化为1。
2)找出与set集中的点相连的,花费最小的并且不再set集中的点,加入set集中。为了计算的方便,先将每个节点相连的所有边按权值升序排列。
3)直到所有的点都加到set集中,算法就停止了。
kruskal算法:
1)每次找权值最小的边,如果节点没有访问过,就加到set集中。如果访问过了,就合并两个set集。
2)这里为了剪去不必要的迭代,如果连通区域为1,并且所有的点都已经访问过了,就退出。

参考代码:

prim算法的代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

struct NODE
{
	int to;
	int w;
};

NODE Map[501][501];//Map[i][0].to存放节点i相邻的点的个数
bool used[501];
int set[501];

int compare(const void *a, const void *b)
{
	NODE *p1 = (NODE *)a;
	NODE *p2 = (NODE *)b;
	return p1->w - p2->w;
}

void GetMap(int n)
{
	for(int i = 1; i <= n; ++i)
		qsort(&Map[i][1], Map[i][0].to, sizeof(Map[i][0]), compare);
}

int Prim(int n)
{
	int num = 1;//set集合内点的个数
	int i,j;
	int ans = 0;
	NODE temp;
	set[0] = 1;
	used[1] = true;	
	while(num < n)
	{
		temp.to = -1;
		temp.w = 101;
		for(i = 0; i < num; ++i)
		{
			for(j = 1; j <= Map[set[i]][0].to; ++j)
			{
				if(!used[Map[set[i]][j].to])
				{
					if(Map[set[i]][j].w < temp.w)
						temp = Map[set[i]][j];
					break;
				}
			}//end for j
		}//end for i
		if(temp.to != -1)
		{
			ans += temp.w;
			set[num++] = temp.to;
			used[temp.to] = true;
		}
	}//end for while
	return ans;
}

int main()
{
	int t,i;
	int v,e;
	int a,b,c;
	int ans;
	scanf("%d", &t);
	while(t--)
	{
		memset(used,0,sizeof(used));
		scanf("%d %d", &v, &e);
		for(i = 0; i <= v; ++i)
			Map[i][0].to = 0;

		for(i = 0; i < e; ++i)
		{
			scanf("%d %d %d", &a, &b, &c);
			++(Map[a][0].to);
			++(Map[b][0].to);
			Map[a][Map[a][0].to].to = b;
			Map[a][Map[a][0].to].w = c;
			Map[b][Map[b][0].to].to = a;
			Map[b][Map[b][0].to].w = c;
		}
		scanf("%d", &b);
		for(i = 1; i < v; ++i)
		{
			scanf("%d", &a);
			b = b < a ? b : a;
		}
		GetMap(v);
		ans = Prim(v);
		ans += b;
		printf("%d\n", ans);
	}
	return 0;
}

kruskal算法的代码:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

struct EDGE
{
	int from;
	int to;
	int w;
};

EDGE edge[124760];//所有的边
bool used[501];
int set[501];

int compare(const void *a, const void *b)
{
	EDGE *p1 = (EDGE *)a;
	EDGE *p2 = (EDGE *)b;
	return p1->w - p2->w;
}

int find(int k)
{
	int r = set[k];
	while(r != set[r])
		r = set[r];
	return r;
}

void Merge(int r1, int r2)
{
	if(r1 < r2)
		set[r2] = r1;
	else
		set[r1] = r2;
}

int Kruskal(int v, int e)
{
	int ans = 0;
	int t, num;//t为连通区域的个数,num为已访问的节点的个数
	int r1, r2;
	t = num = 0;
	while(num != v && t != 1)
	{
		for(int i = 0; i < e; ++i)
		{
			if(!used[edge[i].from])
			{
				++t;
				++num;
				used[edge[i].from] = true;
			}
			if(!used[edge[i].to])
			{
				++t;
				++num;
				used[edge[i].to] = true;
			}

			r1 = find(edge[i].from);
			r2 = find(edge[i].to);
			if(r1 != r2)
			{
				--t;
				Merge(r1, r2);
				ans += edge[i].w;
			}
		}//end for i
	}//end while
	return ans;
}

int main()
{
	int t,i;
	int v,e;
	int a,b,c;
	int ans;
	scanf("%d", &t);
	while(t--)
	{
		memset(used,0,sizeof(used));
		scanf("%d %d", &v, &e);

		for(i = 1; i <= v; ++i)
			set[i] = i;

		for(i = 0; i < e; ++i)
		{
			scanf("%d %d %d", &a, &b, &c);
			edge[i].from = a;
			edge[i].to = b;
			edge[i].w = c;		
		}
		qsort(edge, e, sizeof(edge[0]), compare);
		scanf("%d", &b);
		for(i = 1; i < v; ++i)
		{
			scanf("%d", &a);
			b = b < a ? b : a;
		}
		ans = Kruskal(v, e);
		ans += b;
		printf("%d\n", ans);
	}
	return 0;
}

由于prim方法针对节点,而kruskal方法针对边,所以二者的数据结构有点不一样。

原文地址:https://www.cnblogs.com/xinyuyuanm/p/3027189.html