poj1751 最小生成树(Prim算法和Kruskal算法)

The island nation of Flatopia is perfectly flat. Unfortunately, Flatopia has a very poor system of public highways. The Flatopian government is aware of this problem and has already constructed a number of highways connecting some of the most important towns. However, there are still some towns that you can’t reach via a highway. It is necessary to build more highways so that it will be possible to drive between any pair of towns without leaving the highway system.

Flatopian towns are numbered from 1 to N and town i has a position given by the Cartesian coordinates (xi, yi). Each highway connects exaclty two towns. All highways (both the original ones and the ones that are to be built) follow straight lines, and thus their length is equal to Cartesian distance between towns. All highways can be used in both directions. Highways can freely cross each other, but a driver can only switch between highways at a town that is located at the end of both highways.

The Flatopian government wants to minimize the cost of building new highways. However, they want to guarantee that every town is highway-reachable from every other town. Since Flatopia is so flat, the cost of a highway is always proportional to its length. Thus, the least expensive highway system will be the one that minimizes the total highways length.
Input
The input consists of two parts. The first part describes all towns in the country, and the second part describes all of the highways that have already been built.

The first line of the input file contains a single integer N (1 <= N <= 750), representing the number of towns. The next N lines each contain two integers, xi and yi separated by a space. These values give the coordinates of i th town (for i from 1 to N). Coordinates will have an absolute value no greater than 10000. Every town has a unique location.

The next line contains a single integer M (0 <= M <= 1000), representing the number of existing highways. The next M lines each contain a pair of integers separated by a space. These two integers give a pair of town numbers which are already connected by a highway. Each pair of towns is connected by at most one highway.
Output
Write to the output a single line for each new highway that should be built in order to connect all towns with minimal possible total length of new highways. Each highway should be presented by printing town numbers that this highway connects, separated by a space.

If no new highways need to be built (all towns are already connected), then the output file should be created but it should be empty.
Sample Input
9
1 5
0 0
3 2
4 5
5 1
0 4
5 2
1 2
5 3
3
1 3
9 7
1 2
Sample Output
1 6
3 7
4 9
5 7
8 3

(1)最小生成树的经典问题–建路。输入各个路口的坐标(这处理一下就变成了点于点之间的距离(这里等价为建路费,因为路越长越多钱)),再告诉哪些路是已经建成的了。
虽然想到要用最小生成树做,无奈之前学那个并查集的时候没有学有压缩路径的,所以导致一直tle,按路的长度从小排到大,然后从小开始选路,直到成为一颗完整的树,即所有路口都可以相通了,这是Kruskal算法。因为这道题花的时间很多,也顺便把之前没学的Prim 给补上,这个算法的大概意思是:
(2) 设立两个集合,一个集合V为已经考虑定的点,一个为未考虑到的点的集合U,初始转态,V为空,那么当所有点都放进集合U时说明建图成功了,那么要如何考虑点放的先后顺序呢?U中各点离V中各点都有一个距离(这里指的是两个点间可以建路的,这道题任意两个点都可以建路),找到一个最短距离(比如U中的a点,这里的最短距离指的是a点离V中所有点距离的最短距离),那么现在U中各点都对应一个最短距离,找到其中的最短距离,这个距离会对应两个点,一个是V中点,一个是U中的点,那么U中的点就是我们要加进V中的点。注意的是因为V中新增了点,那么U中的点到V中的点的最小距离就可能发生变化了,所以需要更新。
(3) 第一步随便找一个点加入V中,第二步找从U中找一个距V中各点的最短距离(如(2)所说)(此时只有一个点,这找的是离该点最短距离的那个点)之后就像(2)所说直到U为空集。

这里简单解释一下为什么Prim算法能成为一颗最小权值和的树。
更基于一个贪心的思维,就是比如V中已经有四个点,那么现在权值和就是要把四个点(从N个点找四个点)形成连通分量图的最小权值,那么现在再找一个离V中各点最近的点加进去,就会变成把五个点构成连通分量图的最小权值和(所谓的连通分量图就是要图中任意两点间可以到达),以此进行变成六个,七个,直至所有点都进去,比如共有N个,那么加到最后就是要把N个点构成连通图的最小权值和,即我们所要的结果

个人觉得用Kruskal算法会更容易一点,如果有先学习了并查集会更容易上手和理解。Prim算法有点难理解。但Kruskal算法明显要比Prim算法花的时间长在该题中,或者是我Kruskal算法还可以继续压缩?若不可以数据再多点就可能只能用Prim算法了,所以有备无患,两个都掌握比较好。以下是两个算法的关于该题的ac代码

Kruskal算法

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>
#include <stack>
#include<queue>
using namespace std;
const int N = 755; int ji[N],s1[N],s2[N],jiji[N],kkk[N][N],flag,du[N];
struct node
{
	int x, y;double z;
}p[N*N];
int cmp(node a, node b)
{
	return a.z < b.z;
}
int asd(int x)//这里要对找父节节点进行压缩,不然每次都要通个很多个人找会浪费很多时间,所以最好就是回溯时把所有点都指向同一个父节点,下次再找相同点时,基本就只需要找一次就个找到了。
{   if (x != ji[x])
		ji[x] = asd(ji[x]);
		return ji[x];
}
void bcj(int x, int y)
{
	int a = asd(x), b = asd(y);
	if (a != b)
	{
		if (du[a] > du[b])//这里进行二次压缩,将小树并到大树上。
		{
			ji[b] = a;
			du[a]+= du[b];
		}
		else ji[a] = ji[b];
		du[b] += du[a];
	}
}
void bcj2(int x, int y)
{
	int a = asd(x), b = asd(y);
	if (a != b)
	{
		if (du[a] > du[b])
		{
			ji[b] = a;
			du[a] += du[b];
		}
		else {
			ji[a] = ji[b];
			du[b] += du[a];
		}
		kkk[x][y] = 1;//把有加入到该树的枝节(即路)标记一下,因为有些路已经先有了的,所以用了一个比较笨的办法,写两次,一个是上面那个加入枝节时就不用标记了
	
	}
}
int main()
{
	int n, m,k,v,x2;
	while (cin >> n)
	{
		k = -1; flag = 0;
		memset(kkk, 0, sizeof(kkk));
		for (int i = 0; i <N; i++)
			du[i] = 1;
		for (int i = 1; i <= n; i++)
			scanf("%d%d", &s1[i],&s2[i]);
		for (int i = 1; i <=n; i++)
			ji[i] = i;
		for(int i=1;i<=n;i++)
			for (int j = i+1; j <=n; j++)
			{
				k++;
				p[k].x = i; p[k].y = j;
				p[k].z = (s1[i] - s1[j])*(s1[i] - s1[j]) + (s2[i] - s2[j])*(s2[i] - s2[j]);
			}
		cin >> m; int a, b;
		for (int i = 0; i < m; i++)
		{
			scanf("%d%d", &a, &b);
			bcj(a, b);
		}
	
		sort(p, p + k + 1, cmp);
		
		for (int i = 0; i <= k; i++)
		{
			v = p[i].x;
			x2 = p[i].y;
			if (ji[v] != ji[x2])
			{
				bcj2(v, x2);
			}
		}
		for(int i=1;i<=n;i++)
			for (int j = 1; j <= n; j++)
			{
				if (kkk[i][j])
				{
					flag = 1;
					printf("%d %d\n", i, j);
				}
			}
		if (!flag)cout << endl;
		
	}
}在这里插入代码片

Prim 算法

#include <iostream>
#include <algorithm>
#include <iostream>
#include <string>
#include <stdio.h>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <math.h>
#include <climits>
#include <iomanip>
#include <queue>
#include<vector>
using namespace std;

const int maxn = 755;
const int inf = 0x3f3f3f;
int q[maxn][maxn];
int s1[maxn], s2[maxn],ji[maxn],l[maxn],vis[maxn];
int n, m,k,flag;
void prim()
{
	flag = 0;
	memset(vis, inf, sizeof(vis));//VIS数组放的变是U中各点离V中点的最短距离
	memset(ji, 0, sizeof(ji));//把已经放入V中点标记一下,避免V中点重复。
	for (int i = 2; i <= n; i++)
	{
		vis[i] = q[1][i];
		l[i] = 1;//l数组存为最小距离的路两端中在V中的一端,一开始都V中只有一这个点嘛,所以最小距离就是U中各点与一的点,那在V中当然是点一了
	}
	for (int i = 2; i <= n; i++)
	{
		int max = inf;
		for (int i = 2; i <= n; i++)
		{
			if (vis[i] < max&&!ji[i])//从最小距离中找到最小距离
			{
				max = vis[i];
				k = i;
			}
		}
		ji[k] = 1;    
		if (q[k][l[k]])//因为已经把已经建好的路标为零了(事实上也要标成零,如果不标成0,就可能不把建好的路算进来了),所以如果选出的路是已经建好的,就不用再建了,即不用输出
		{
			flag = 1; printf("%d %d\n", k,l[k]);//那么K是从U中刚调来的点,l[k]是V中的点。
	}
		for (int i = 1; i <= n; i++)//因为V中加入了新的点,所以U中各点离V中的最小巨离要更新一下
		{
			if (!ji[i] && vis[i] > q[k][i])
			{
				vis[i] = q[k][i];
				l[i] = k;
			}
		}

	}return;
}
int main()
{
	cin >> n;
		
		for (int i = 1; i <= n; i++)
			scanf("%d%d", &s1[i], &s2[i]);

		for (int i = 1; i <= n; i++)
		{
			for (int j = 1; j <= n; j++)
			{
				q[i][j] = q[j][i] = (s1[i] - s1[j])*(s1[i] - s1[j]) + (s2[i] - s2[j])*(s2[i] - s2[j]);
			}
		}int c, d;
		scanf("%d",&m);
		while (m--)
		{
			scanf("%d%d", &c, &d);
			q[c][d] = q[d][c] = 0;
		}
		prim();
		if (!flag)printf("\n");

	
}在这里插入代码片
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值