poj 1679次小生成树的两种解法: Prim和Kruskal

4 篇文章 0 订阅
4 篇文章 0 订阅

poj 1679 The Unique MST

题目大意:给一个连通图,n个点,m个边,然后求出次小生成树和最小生成树的权值是否相等,也就是判断最小生成树是否唯一,如果唯一就输出最小生成树的权值,不唯一输出Not Unique!

一下讲解内容转自:MATO IS NO.1

以下的T全部指G的最小生成树。

(1)Prim:

 设立数组F,F[x][y]表示T中从x到y路径上的最大边的权值。F数组可以在用Prim算法求最小生成树的过程中得出。每次将边(i, j)加入后(j是新加入树的边,i=c[j]),枚举树中原有的每个点k(包括i,但不包括j),则F[k][j]=max{F[k][i], (i, j)边权值},又由于F数组是对称的,可以得到F[j][k]=F[k][j]。然后千万记住将图G中的边(i, j)删除(就是将邻接矩阵中(i, j)边权值改为∞)!因为T中的边是不能被加入的。等T被求出后,所有的F值也求出了,然后,枚举点i、j,若邻接矩阵中边(i, j)权值不是无穷大(这说明i、j间存在不在T中的边),则求出{(i, j)边权值 - F[i][j]}的值,即为加入边(i, j)的代价,求最小的总代价即可。 另外注意三种特殊情况:【1】图G不连通,此时最小生成树和次小生成树均不存在。判定方法:在扩展T的过程中找不到新的可以加入的边;【2】图G本身就是一棵树,此时最小生成树存在(就是G本身)但次小生成树不存在。判定方法:在成功求出T后,发现邻接矩阵中的值全部是无穷大;【3】图G存在平行边。这种情况最麻烦,因为这时代价最小的可行变换(-E1, +E2)中,E1和E2可能是平行边!因此,只有建立两个邻接矩阵,分别存储每两点间权值最小的边和权值次小的边的权值,然后,每当一条新边(i, j)加入时,不是将邻接矩阵中边(i, j)权值改为无穷大,而是改为连接点i、j的权值次小的边的权值。

代码(这个代码是很久以前写的,有点丑了,注释也少,不过prim算法相对简单的多)

#include <iostream>
#include <string.h>
#include <stdio.h>
#define M 105
using namespace std;

int map1[M][M],map2[M][M],minmap[M][2],n,m,mst;
//map1是原图,map2[i][j]表示最小生成树上i到j的最长的边的值,
//mst表示最小生成树的值,那么此短路就是遍历所有i到j,
//求mst-map2[i][j]+map1[i][j]的嘴小指,就是次小生成树
bool demap[M];
void prim()
{
    memset(map2,100,sizeof(map2));
    memset(minmap,100,sizeof(minmap));
    memset(demap,true,sizeof(demap));
	int star,i,j,k,m;
	mst=0;
	demap[star=1]=false;
	while(1)
	{
		map2[star][star]=0;
		for(i=1,m=100000;i<=n;i++)
		{
			if(map1[star][i]<minmap[i][0])
			{
				minmap[i][1]=star;
				minmap[i][0]=map1[star][i];
			}
			if(demap[i]&&minmap[i][0]<m)
			{
				m=minmap[i][0];
				k=i;
			}
		}

		if(m==100000)
			break;
		for(i=1;i<=n;i++)
			if(!demap[i])
				map2[k][i]=map2[i][k]=max(map2[minmap[k][1]][i],minmap[k][0]);
		map1[k][minmap[k][1]]=map1[minmap[k][1]][k]=1684300900;
		mst+=minmap[star=k][0];
		demap[star]=false;

	}
	int Max=1684300900;
    for(i=1;i<=n;i++)
        for(j=1;j<=n;j++)
            if(map1[i][j]!=1684300900)
            Max=min(mst-map2[i][j]+map1[i][j],Max);
    if(Max!=mst)
        cout<<mst<<endl;
    else
        cout<<"Not Unique!"<<endl;
}


int main()
{
	int N;cin>>N;
	while(N--)
	{
        memset(map1,100,sizeof(map1));
		int i,j,k,x,y,z;
		cin>>n>>m;
		for(i=0;i<m;i++)
		{
			scanf("%d%d%d",&x,&y,&z);
			map1[x][y]=map1[y][x]=min(z,map1[x][y]);
		}
		prim();
	}
}


(2)Kruskal:

Kruskal算法也可以用来求次小生成树。在准备加入一条新边(a, b)(该边加入后不会出现环)时,选择原来a所在连通块(设为S1)与b所在连通块(设为S2)中,点的个数少的那个(如果随便选一个,最坏情况下可能每次都碰到点数多的那个,时间复杂度可能增至O(NM)),找到该连通块中的每个点i,并遍历所有与i相关联的边,若发现某条边的另一端点j在未选择的那个连通块中(也就是该边(i, j)跨越了S1和S2)时,就说明最终在T中"删除边(a, b)并加入该边"一定是一个可行变换,且由于加边是按照权值递增顺序的,(a, b)也一定是T中从i到j路径上权值最大的边,故这个可行变换可能成为代价最小的可行变换,计算其代价为[(i, j)边权值 - (a, b)边权值],取最小代价即可。注意,在遍历时需要排除一条边,就是(a, b)本身(具体实现时由于用DL边表,可以将边(a, b)的编号代入)。另外还有一个难搞的地方:如何快速找出某连通块内的所有点?方法:由于使用并查集,连通块是用树的方式存储的,可以直接建一棵树(准确来说是一个森林),用“最左子结点+相邻结点”表示,则找出树根后遍历这棵树就行了,另外注意在合并连通块时也要同时合并树。 对于三种特殊情况:【1】图G不连通。判定方法:遍历完所有的边后,实际加入T的边数小于(N-1);【2】图G本身就是一棵树。判定方法:找不到这样的边(i, j);【3】图G存在平行边。这个对于Kruskal来说完全可以无视,因为Kruskal中两条边只要编号不同就视为不同的边。 其实Kruskal算法求次小生成树还有一个优化:每次找到边(i, j)后,一处理完这条边就把它从图中删掉,因为当S1和S2合并后,(i, j)就永远不可能再是可行变换中的E2了。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
const int N = 110;
const int M = N * (N - 1);
using namespace std;

struct Edg{//edg1用于储存图,edg2用于储存集合
	int key, id, next;
}edg1[M], edg2[M];
struct Edg1{//edg3用于储存边
	int x, y, len;
	bool operator < (const Edg1 a)const{
		return len < a.len;
	} 
}edg3[M];

int nod1[N], nod2[N], nod2_num[N], k1, k2;//nod1、k1和nod2、k2分别对应edg1和edg2;邻接表存图
int n, m, bing[N];
int mst, diff;//mst是最小生成树的权值,diff表示次小生成树与最小生成树的差值,如果是0就代表次小生成树的权值和最小生成树一样

void addedg(struct Edg edg[], int nod[], int x, int y, int len, int *k){//加边操作
	edg[k1].key = len;
	edg[k1].id = y;
	edg[k1].next = nod[x];
	nod[x] = (*k) ++;
}

int find(int x){
	return (bing[x] != -1 ? find(bing[x]) : x);
}

void init(){//预处理函数
	k1 = k2 = mst = 0;
	diff = (1 << 31) - 1;
	memset(nod1, -1, sizeof(nod1));
	memset(nod2, -1, sizeof(nod2));
	memset(bing, -1, sizeof(bing));
	for(int i = 0;i < n; i++)
		nod2_num[i] = 1;
	for(int i = 0; i < m ; i ++){
		cin >> edg3[i].x >> edg3[i].y >> edg3[i].len;
		addedg(edg1, nod1, edg3[i].x, edg3[i].y, edg3[i].len, &k1);
		addedg(edg1, nod1, edg3[i].y, edg3[i].x, edg3[i].len, &k1);
	}
	sort(edg3,edg3 + m);
}

void fun(int x, int y, int len, int t){//遍历所有与点x相连接的点并且该点在 要合并的y的集合里
	for(int k = nod1[x]; k != -1; k = edg1[k].next){
		int v = edg1[k].id;
		if(v == t) continue;
		v = find(v);
		if(v == y)
			diff = min(edg1[k].key - len,diff);
	}
}

void Kruskal(){
	int x, y, num = 0;
	for( int i = 0; i < m && num + 1 != n; i ++){
		x = find(edg3[i].x);
		y = find(edg3[i].y);
		int t = edg3[i].y;
		if(x != y){
			mst += edg3[i].len;
			if(nod2_num[x] > nod2_num[y]){
				swap(x, y);
				t = edg3[i].x;
			}
			fun(x, y, edg3[i].len, t);
			for(int j =nod2[x]; j != -1; j = edg2[j].next){
				fun(edg2[j].id, y, edg3[i].len, t);
			}
			for(int j = nod2[x]; j != -1; j = edg2[j].next){//合并要合并的两个子树
				addedg(edg2, nod2, y, edg2[j].id, 0, &k2);
			}
			bing[x] = y;
		}
	}
}

void pri(){
	if(diff)	cout << mst << endl;
	else cout<<"Not Unique!"<<endl;
}
int main(){
	//freopen("1.txt","r",stdin);
	int T;	cin >> T;
	while(T --){
		cin >> n >> m;
		init();
		Kruskal();
		pri();
	}
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值