最小生成树练习总结

最小生成树

最小生成树简称MST(Minimum Spanning Tree),最小生成树算法有Prim算法和Kruskal算法,prim算法其实就是在dijkstra算法上面稍微修改一哈,当然也可以加heap优化,而Kruskal就有点意思了,因为这个算法在解决判断环的问题的时候用了并查集。还是先来书上的描述
在这里插入图片描述
书上的代码:

在这里插入图片描述
说实话这个并查集用的是真的溜~

poj2421 poj1287 poj2031
这两道题比较基础,用prim和kruskal都可以做出来,但是这两个算法在效率上有点小区别,kruskal要稍微快一些
就拿poj2421 来说,直接用图说话
在这里插入图片描述

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
typedef struct Edge{
	int sour, dest, value;
}Edge;
Edge edge[105*105];
int length, k = 0, shuzu[105][105], answer = 0;
int parent[105], rank[105];

bool cmp(Edge a, Edge b){
	return a.value < b.value;
}

void addedge(int sour, int dest, int value){
	edge[k].sour = sour;
	edge[k].dest = dest;
	edge[k].value = value;
	k ++;
}

int find(int input){
	if(input == parent[input])
		return input;
	else
		return parent[input] = find(parent[input]);
}

void unite(int x, int y){
	x = find(x);
	y = find(y);
	if(x == y) return;
	if(rank[x] < rank[y]){
		parent[x] = y;
	}else{
		parent[y] = x;
		if(rank[x] == rank[y]) rank[x] ++; 
	}
}

void kruskal(){
	sort(edge, edge+k, cmp);
	for(int i = 0; i < k; i++){
		int sour = edge[i].sour, dest = edge[i].dest, value = edge[i].value;		
		if(find(sour) == find(dest)) continue;				
		unite(sour, dest);
		answer += value;
	}
	
	cout << answer << endl;
}

int main(){
	scanf("%d", &length);	
	for(int i = 1; i <= length; i++){
		for(int j = 1; j <= length; j++){
			scanf("%d", shuzu[i] + j);
		}
	}
	
	int jishu, x, y;
	scanf("%d", &jishu);
	for(int i = 0; i <= length; i++) parent[i] = i, rank[i] = 0;
	for(int i = 0; i < jishu; i++){
		scanf("%d%d", &x, &y);
		shuzu[x][y] = shuzu[y][x] = 0;
		unite(x, y);
	}
	
	for(int i = 1; i <= length; i++){
		for(int j = 1; j <= length; j++){
			if(shuzu[i][j]) addedge(i, j, shuzu[i][j]);
		}
	}
	
	kruskal();
	return 0;
}

poj2349 poj3241这两道题要稍微绕一哈,第一道题还好,但是第二道题有点小东西
这就主要说第二题,第二题确实就是要求MST,然后根据要分成几组,找最小生成树中的第几个大的数。就跟poj2349一样,而且人家poj2349一哈就跑出来了,但是poj3241就没得那么顺利了,我甚至都有点怀疑是不是不该用MST,然后我又想是不是输入出了问题,就像前面最短路径有道题一样,那道Ferry(poj3377)就是我输入long long出的问题,但是这道题我就是用的int输入的嘛,那到底是啥子问题喃? 说出来你可能不得行,只是因为数组的问题,因为我储存a和b的值的时候用的是一个二维数组,你肯定会想用二维数组又咋了嘛,对的三~,错的!因为寻址的问题,二维数组的本质就是一维数组,如果频繁的用二维数组的话,就要出事了。我这就有证据的,因为如果我把这个二维数组变成用结构体存储的话就过了,而且是险过。在这里插入图片描述
但是现在又出了一个问题,那就是我偶然发现了一个效率更高的方法,那就是拿到所有的a和b,把他们全部排序,然后效率更是截然不同在这里插入图片描述
关键是按理来说拍了序应该没得啥子区别才对三,因为循环那些都是要走的三,又不可能说排了序就要少些循环,我还在研究。。。

POJ1751
这道题其实和上面的题也很像,也是求MST,而且也是会提前给出一些边已经连接了,然后喊你输出要连接哪些边,我第一反应就是kruskal,因为并查集是肯定要用的,因为已经有一些边连接了,在后面求MST的过程中很有可能连接到边会和之前题目给出的变形成环,所以并查集是必须的,然后我用kruskal就超内存了,我举得这道题就是故意不然你用kruskal,可能故意就是要让你用prim来解决,而且用prim的时候还有个小问题。
prim再求MST的时候和dijkstra是一样的,只不过prim只用比单独的一条边而dijkstra要是把前面的边都加起来,而基本流程就是先根据上一次找的那个最小的点A来更新其他的边,然后再从中找出最小的那个点B(在这道题中我为了效率就合并了),然后就通过并查集判断点A 和 B 是否会形成环,如果不会就打印。 乍一看好像就是那么回事,但是这就有个致命问题,虽然点B是在点A更新边的过程找到的,但是有个能这次更新中点A没有更新到点B,而点B确实有前面某个早就确定的点更新的。
解决方法可以通过最短路径中学到的path来记录,我在这道题中是也是这么做的只不过我用的名字的是from数组
还有个问题就是关于Double类型初始化为inf,如果这样来:

#define inf = 0x7F7F7F7F;(初始化double的时候用0x3F3F3F3F就不好了)
double temp1 = inf;
int a = inf;
printf("%d\n%f", a, temp1);

在这里插入图片描述

double temp2;
memset(&temp2, 0x7F, sizeof(temp2));
cout << temp2 << endl;

在这里插入图片描述
double是8个字节,long是4个字节,long long也是8个字节,那为啥子这里 不传0x7F7F7F7F7F7F7F7F喃?首先double的存储并不像long long直接就可以通过十六进制转成十进制,double之所以可以存那么大的数,是因为他是的结构比较特殊,可以参考这篇博客
Double_Float的存储结构
,所以上面两个例子就是因为编译器没有把0x7F7F7F7F直接送到double存储的的那八个字节中,而是根据double的特殊结构是的最后转化为十进制的值为0x7F7F7F7F

#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
using namespace std;
#define maxn 753
typedef struct Node{
	double x, y;
}Node;
Node input[maxn];
double inf ;
double shuzu[maxn][maxn];
int length;
int parent[maxn], rank[maxn], from[maxn];
double tag[maxn];
bool flag[maxn];

int find(int input){
	if(input == parent[input])
		return input;
	else
		return parent[input] = find(parent[input]);
}

void unite(int x, int y){
	x = find(x);
	y = find(y);
	if(x == y) return ;
	if(rank[x] < rank[y]){
		parent[x] = y;
	}else{
		parent[y] = x;
		if(rank[x] == rank[y]) rank[x] ++;
	}
}

void prim(){
	memset(tag, 0x7f, sizeof(tag));
	memset(flag, 0, sizeof(flag));
	memset(from, 0, sizeof(from));
	int father, biaoji = 0, dangqian, index = 1;
	double zuixiao;
	tag[1] = 0; flag[1] = 1;
	for(int cishu = 0; cishu < length; cishu ++){
		zuixiao = inf, dangqian = -1;
		for(int i = 1; i <= length; i++){
			if(flag[i]) continue;
			//better
			if(shuzu[i][index] < tag[i]) tag[i] = shuzu[i][index], from[i] = index;
			if(zuixiao > tag[i]) zuixiao = tag[i], dangqian = i, father = from[i];
		}
		
		if(dangqian == -1) return ;
		if(find(father) != find(dangqian)){
			printf("%d %d\n", father, dangqian);			
			biaoji = 1;
		}
		unite(father, dangqian);
		index = dangqian;
		flag[index] = 1;		
	}
	if(biaoji == 0) cout << "" ;
} 

int main(){
	memset(shuzu, 0x7f, sizeof(shuzu));
	inf = shuzu[0][0];	
	scanf("%d", &length);
	double r;
	for(int i = 1; i <= length; i++){
		scanf("%lf%lf", &input[i].x, &input[i].y);
		for(int j = 1; j < i;  j++){
			r = sqrt(pow(input[i].x-input[j].x,2) + pow(input[i].y-input[j].y,2));
			if(shuzu[i][j] > r) shuzu[i][j] = shuzu[j][i] = r;
		}
	}
	int m, x, y;
	scanf("%d", &m);
	for(int i = 1; i <= length; i++) parent[i] = i, rank[i] = 0;
	for(int i = 0; i < m; i++){
		scanf("%d%d", &x, &y);
		shuzu[x][y] = shuzu[y][x] = 0; 
		unite(x, y);		
	}
	prim();
	return 0;
}

poj1679
这道题我第一反应就是这应该是一道上档次的题,因为老实讲MST的时候也说过最小生成树是不唯一的,这道题就是来判断这个东西,我的第一感觉就是之所以MST不是唯一的及时因为有环的情况而且起码有两条边相同的情况,所以我就想用kruskal,然后再用并查集判断是否存在环的时候做文章,但是越想越复杂,因为这个环中可能还有其他的小环,而且还有可能一个点连了几个环,这就有点恼火了,因为还要区别是哪个环中得边,确实最后就放弃这种高大上的办法了,然后只有用最暴力的枚举,也就是每个点挨到挨到的用prim,然后判断最小生成树的边有没有不同的,我一直有点担心这会超时,但是还好。。

#include<iostream>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
#define maxn 102
typedef struct Node{
	int sour, dest;
	long long value;
	Node(int s, int d, long long v){sour = s; dest = d; value = v;}
	bool operator < (Node const &a) const{
		return value > a.value;
	}
}Node;
int v, length;
long long answer;
long long shuzu[maxn][maxn];
long long tag[maxn];
bool flag[maxn], compare[maxn][maxn], zhanshi[maxn][maxn];

bool prim(bool panduan, int start){
	memset(tag, 0x3f, sizeof(tag));
	memset(flag, 0, sizeof(flag));
	memset(zhanshi, 0, sizeof(zhanshi));
	priority_queue<Node> que;
	que.push(Node(0, start, 0));
	
	while(!que.empty()){
		Node node = que.top(); que.pop();
		int index = node.dest, sour = node.sour;
		long long temp = node.value;
		if(flag[index]) continue;
		flag[index] = 1;
		tag[index] = temp;
		zhanshi[sour][index] = zhanshi[index][sour] = 1;
		answer += temp; 
		for(int i = 1; i <= v; i++){
			if(flag[i]) continue;
			if(tag[i] > shuzu[index][i]) que.push(Node(index, i, shuzu[index][i]));
		}
	}

	if(panduan){
		for(int i = 1; i <= v; i++){
			for(int j = 1; j <= v; j++){
				compare[i][j] = zhanshi[i][j];
			}
		}
		return true;
	}else{
		for(int i = 1; i <= v; i++){
			for(int j = 1; j < i; j ++){
				if(compare[i][j] != zhanshi[i][j]) return false;	
			}
		}
	 	return true;
	}
}

int main(){
	int jishu, x, y;
	long long vv;
	scanf("%d", &jishu);
	while(jishu --){		
		memset(shuzu, 0x3f, sizeof(shuzu));
		memset(compare, 0, sizeof(compare));
		scanf("%d%d", &v, &length);
		for(int i = 0; i < length; i++){
			scanf("%d%d%I64d", &x, &y, &vv);
			shuzu[x][y] = shuzu[y][x] = vv;
		}
	
		answer = 0;
		prim(true, 1);
		int biaoji = 0;
		for(int i = 2; i <= v; i ++){
			answer = 0;
			if(!prim(false, i)) {biaoji = 1; cout << "Not Unique!" << endl; break;}
		}
		if(biaoji == 0) cout << answer << endl;
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Two_Punch/article/details/83515096