最小生成树(poj1251 poj1861 poj1789)

1. Prim算法

该算法与Dijkstra算法十分相似,不同的地方在于Dist矩阵的算法有细微差别。见下面的实现:

/*
poj 1251
time: 0ms
memory:220k
PS:该题目的输入中每行结尾可能有几个空格,要是用scanf函数,字符串的输入要用字符数组,而不能用单个字符。或者用cin输入。
*/
#include <iostream>
using namespace std;

#define MaxNode 27
#define MaxEdge 150
#define INF 1000000


struct VertexEntry
{
	int next, e, cost;
};

VertexEntry vertex[MaxEdge + MaxNode]; 
int Cost[MaxNode];
int Path[MaxNode];
int Known[MaxNode];

void Init(int Num)
{
	int i, j, k = Num + 1, cost, num;
	char id;

	for(i = 1; i <= Num; ++i)
	{
		Cost[i] = INF;
		Path[i] = 0;
		Known[i] = 0;
		vertex[i].next = 0;
	}
	Cost[1] = 0;

	while(--Num)
	{
		cin >> id >> num;
		i = id - '@';
		while(num--)
		{
			cin >> id >> cost;
			j = id - '@';
			vertex[k].cost = cost;
			vertex[k].e = j;
			vertex[k].next = vertex[i].next;
			vertex[i].next = k++;

			vertex[k].cost = cost;
			vertex[k].e = i;
			vertex[k].next = vertex[j].next;
			vertex[j].next = k++;			
		}
	}
}

int Prim(int Num)
{
	int i, j, min, sum = 0;

	while(1)
	{
		min = INF;
		for(i = 1; i <= Num; ++i)
		{
			if(!Known[i] && Cost[i] < min)
			{
				min = Cost[i];
				j = i;
			}
		}
		if(min == INF)
			break;
		
		Known[j] = 1;

		i = j;
		while(vertex[j].next)
		{
			j = vertex[j].next;
			if(!Known[vertex[j].e] && vertex[j].cost < Cost[vertex[j].e])
			{
				Cost[vertex[j].e] = vertex[j].cost;
				Path[vertex[j].e] = i;
			}
		}
	}
	for(i = 1; i <= Num; ++i)
	{
		sum += Cost[i];
	}
	return sum;
}

int main()
{
	int Num;
	cin >> Num;
	while(Num)
	{
		Init(Num);
		cout << Prim(Num) << endl;		
		cin >> Num;
	}
	return 0;
}

PS:开始我使用scanf搭配char,一直不过,一查才知道数据输入可能每行结尾有多余的空格。虽然空格、制表符和新行符都用做域分割符号,但读单字符操作中却按一般字符处理。例如,对输入流 "x y" 调用:

scanf("%c%c%c",&a,&b,&c);  //x 在变量 a 中,空格在变量 b 中,y 在变量 c 中

使用scanf则要与char[]搭配才行,如下代码:

/*
poj 1251
time: 0ms
memory: 220k
*/
#include <iostream>
#include <stdio.h>
using namespace std;

#define MaxNode 27
#define MaxEdge 150
#define INF 1000000


struct VertexEntry
{
	int next, e, cost;
};

VertexEntry vertex[MaxEdge + MaxNode]; 
int Cost[MaxNode];
int Path[MaxNode];
int Known[MaxNode];

void Init(int Num)
{
	int i, j, k = Num + 1, cost, num;
	char id[3];			//注意此处

	for(i = 1; i <= Num; ++i)
	{
		Cost[i] = INF;
		Path[i] = 0;
		Known[i] = 0;
		vertex[i].next = 0;
	}
	Cost[1] = 0;

	while(--Num)
	{
		scanf("%s%d", id, &num);
		i = id[0] - '@';		//注意此处
		while(num--)
		{
			scanf("%s%d", id, &cost);	//注意此处
			j = id[0] - '@';
			vertex[k].cost = cost;
			vertex[k].e = j;
			vertex[k].next = vertex[i].next;
			vertex[i].next = k++;

			vertex[k].cost = cost;
			vertex[k].e = i;
			vertex[k].next = vertex[j].next;
			vertex[j].next = k++;			
		}
	}
}

int Prim(int Num)
{
	int i, j, min, sum = 0;

	while(1)
	{
		min = INF;
		for(i = 1; i <= Num; ++i)
		{
			if(!Known[i] && Cost[i] < min)
			{
				min = Cost[i];
				j = i;
			}
		}
		if(min == INF)
			break;
		
		Known[j] = 1;

		i = j;
		while(vertex[j].next)
		{
			j = vertex[j].next;
			if(!Known[vertex[j].e] && vertex[j].cost < Cost[vertex[j].e])
			{
				Cost[vertex[j].e] = vertex[j].cost;
				Path[vertex[j].e] = i;
			}
		}
	}
	for(i = 1; i <= Num; ++i)
	{
		sum += Cost[i];
	}
	return sum;
}	

int main()
{
	int Num;
	cin >> Num;
	while(Num)
	{
		Init(Num);
		cout << Prim(Num) << endl;		
		cin >> Num;
	}
	return 0;
}


2. Kruskal算法

kruskal算法总共选择n- 1条边,(共n个点)所使用的贪婪准则是:从剩下的边中选择一条不会产生环路的具有最小耗费的边加入已选择的边的集合中。注意到所选取的边若产生环路则不可能形成一棵生成树。该算法常与并查集,最小堆联合使用。与prim算法像比较,kruskal算法适用于稠密图,而prim算法对稀疏图和稠密图都较适用,原因就在于边的权的存储方式的不同。kruskal算法常用用结构数组将边存储,而prim算法既可以用二维数组(邻接矩阵)存储边的信息,也可以用邻接表存储。

/*
poj 1861
time: 16ms
memory:428k
*/
#include <iostream>
#include <stdio.h>
using namespace std;


#define MaxNode 1001
#define MaxEdge 15001


typedef int PriorityQueue[MaxEdge];
typedef int DisjSet[MaxNode];




struct EdgeEntry
{
	int n1, n2, w, flag;
};


DisjSet s;					//并查集
EdgeEntry Edge[MaxEdge];
PriorityQueue H;				//最小堆,H[i],i表示在堆里的位置,H[i]是指向Edge的index,通过改变H[i]的取值完成堆的实现
int max_w = 0;					//存储选中边里的最大权值


void Init(int N, int M)
{
	int i, n1, n2, w;
	for(i = 1; i <= N; ++i)
	{
		s[i] = -1;
	}


	for(i = 1; i <= M; ++i)
	{
		scanf("%d%d%d", &n1, &n2, &w);
		Edge[i].n1 = n1;
		Edge[i].n2 = n2;
		Edge[i].w = w;
		Edge[i].flag = 0;
		H[i] = i;			//此处对H[i]初始化,相当于将Edge按顺序放入堆里
	}
}


int Find(int v)
{
	if(s[v] < 0)
		return v;
	else
		return s[v] = Find(s[v]);
}


void Union(int root1, int root2)
{
	if(s[root1] < s[root2])
	{
		s[root1] += s[root2];
		s[root2] = root1;
	}		
	else
	{
		s[root2] += s[root1];
		s[root1] = root2;
	}
}


void PercolateDown(int M, int i)
{	
	int FirstElement = H[i];
	int child;
	for(; 2 * i <= M; i = child)
	{
		child = 2 * i;
		if(2 * i + 1 <= M && Edge[H[2 * i + 1]].w < Edge[H[2 * i]].w)
			++child;
		if(Edge[H[child]].w < Edge[FirstElement].w)		//务必注意此处,使用FirstElement而不是H[i],虽然容易理解,但也很容易犯错
			H[i] = H[child];
		else
			break;
	}
	H[i] = FirstElement;
}


int DeleteMin(int M)
{
	int i, child;
	int MinElement = H[1];
	int LastElement = H[M];


	for(i = 1; i * 2 <= M; i = child)
	{
		child = 2 * i;
		if(2 * i + 1 <= M && Edge[H[2 * i + 1]].w < Edge[H[i * 2]].w)
			++child;
		if(Edge[LastElement].w > Edge[H[child]].w)		//务必注意此处,使用LastElement而不是H[i]
			H[i] = H[child];
		else
			break;
	}
	H[i] = LastElement;
	return MinElement;
}


void Kruskal(int N, int M)
{
	int cnt = 0;
	int root1, root2;
	int Edgeid;
	
	while(cnt < N - 1)
	{
		Edgeid = DeleteMin(M--);
		root1 = Find(Edge[Edgeid].n1);
		root2 = Find(Edge[Edgeid].n2);
		if(root1 != root2)
		{
			if(Edge[Edgeid].w > max_w)
				max_w = Edge[Edgeid].w;	
			
			Edge[Edgeid].flag = 1;
			++cnt;
			Union(root1, root2);
		}
	}
}


int main()
{
	int N, M, i, cnt;
	scanf("%d%d", &N, &M);
	Init(N, M);
	for(i = M / 2; i > 0; --i)
		PercolateDown(M, i);
	Kruskal(N, M);
	printf("%d\n%d\n", max_w, N - 1);
	for(i = 1, cnt = 0; cnt < N - 1; ++i)
	{
		if(Edge[i].flag)
		{
			++cnt;
			printf("%d %d\n", Edge[i].n1, Edge[i].n2);
		}
	}


	return 0;
}

PS: 该题让我纠结很很久,看了disscus后以为一直WA是oj问题,就没在意打算再做下一个kurskal,一定要AC一道,下面那题样例过了,disscus里有人上传了一组数据也测试通过,但提交却一直WA,郁闷死了。随手又测试一组数据,发现确实是自己错了。鉴于该算法看了好几天,实在不想再对着代码检查了(虽然知道代码一定错了),就用gdb一步一步调试,终于发现实在堆的操作错了,就是代码注释的那里出错,其实不难理解,但很容易相当然就容易犯错。这一次又一次的告诉我,别对自己太自信了,出现WA或者RE很大程度上就是自己的问题,要更耐心一点。╮(╯▽╰)╭哎~此外,还要认真读题,别光急着写代码,很容易一开始就错了o(╯□╰)o。

/*
poj 1789
time:766ms
memory:30476k
性能有些差啊,主要是该图是个稠密图,应该用prim算法+邻接矩阵
*/
#include <stdio.h>
#include <string>
#include <iostream>
#include <stdio.h>
using namespace std;

#define MaxNode 2001
#define MaxEdge 1999001

typedef int DisjSet[MaxNode];
typedef int Priority[MaxEdge];

struct EdgeEntry
{
	int n1, n2, w;
};
EdgeEntry Edge[MaxEdge];
string truck_id[MaxNode];
DisjSet s;
Priority H;
int sum;

void Init(int N, int M)
{
	int i, j, n, k = 1;
	for(i = 1; i <= N; ++i)
	{	
		cin >> truck_id[i];
		s[i] = -1;
	}

	for(i = 1; i <= M; ++i)
		Edge[i].w = 0;
	
	for(i = 1; i <= N; ++i)
		for(j = 1; j < i; ++j)
		{
			for(n = 0; n <= 6; ++n)
				Edge[k].w += (truck_id[i][n] != truck_id[j][n]);
			Edge[k].n1 = i;
			Edge[k].n2 = j;
			H[k] = k;
			++k;
		}
		
}	

int Find(int v)
{
	if(s[v] < 0)
		return v;
	else
		return s[v] = Find(s[v]);
}

void Union(int root1, int root2)
{
	if(s[root1] < s[root2])
	{
		s[root1] += s[root2];
		s[root2] = root1;
	}		
	else
	{
		s[root2] += s[root1];
		s[root1] = root2;
	}
}

void PercolateDown(int M, int i)
{	
	int FirstElement = H[i];
	int child;
	for(; 2 * i <= M; i = child)
	{
		child = 2 * i;
		if(2 * i + 1 <= M && Edge[H[2 * i + 1]].w < Edge[H[2 * i]].w)
			++child;
		if(Edge[H[child]].w < Edge[FirstElement].w)
			H[i] = H[child];
		else
			break;
	}
	H[i] = FirstElement;
}

int DeleteMin(int M)
{
	int i, child;
	int MinElement = H[1];
	int LastElement = H[M];

	for(i = 1; i * 2 <= M; i = child)
	{
		child = 2 * i;
		if(2 * i + 1 <= M && Edge[H[2 * i + 1]].w < Edge[H[i * 2]].w)
			++child;
		if(Edge[LastElement].w > Edge[H[child]].w)
			H[i] = H[child];
		else
			break;
	}
	H[i] = LastElement;
	return MinElement;
}

void Kruskal(int N, int M)
{
	int cnt = 0;
	int root1, root2;
	int Edgeid;
	
	while(cnt < N - 1)
	{
		Edgeid = DeleteMin(M--);
		root1 = Find(Edge[Edgeid].n1);
		root2 = Find(Edge[Edgeid].n2);
		if(root1 != root2)
		{			
			sum += Edge[Edgeid].w;
			++cnt;
			Union(root1, root2);
		}
	}
}

int main()
{
	int N, M, i;
	scanf("%d", &N);
	while(N)
	{	
		sum = 0;
		M = (N * N - N) / 2;	
		Init(N, M);
		for(i = M / 2; i > 0; --i)
			PercolateDown(M, i);
		Kruskal(N, M);
		cout << "The highest possible quality is 1/" << sum << "." << endl;
		scanf("%d", &N);
	}
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值