您的位置:首页 > 其它

[入门]最小生成树--poj1258 Agi-Net

2014-07-25 21:07 218 查看
本题题意就是约翰成了村里的头头,然后他感觉很开森,就要把网共享给村名.但是拉网线甚么的是要成本的.然后题目给出一个矩阵,矩阵的某一个元素(i,j)就表示第i个村民到第j个村民那儿拉网线的成本,所以从样例就可以看出(i,j)是等于(j,i)的,然后(i,i)都是0,也就是自己到自己家不需要成本.

生成树的定义神马的百度百科里可以了解.大概就是一个图的子图如果满足连通性,也就是两两点之间都能连通,然后还没有环,它奏是棵树了,美其名曰生成树,其中边权和最小(对应实际问题中的总路程最短,费用最少等等)的生成树就叫最小生成树.求最小生成树有两种算法,其一是Prim算法,其二是Kruskal算法.我分别用两种方法做了一下.

对于图论问题,见图,算法解题,貌似都带一些模式化,比如prim算法用哪种存图方法会更方便操作等等,但是其中的思想在以后学到自己设计算法的份上时价值非凡..

这是Prim算法的代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <climits> //INT_MAX
using namespace std;

const int MAX = 128;
const int INF = INT_MAX>>1;
int cost[MAX][MAX];//cost[u][v]表示边e=(u,v)的权值(不存在时为INF)
int mincost[MAX];//从集合X出发的边到每个顶点的最小权值
bool used[MAX]; //顶点i是否包含在集合X中
int V, E;

int prim(void) {
fill(mincost, mincost+V, INF);
memset(used, false, sizeof(used));

mincost[0] = 0;
int res = 0;

while (true) {
int v = -1;
for (int u = 0; u < V; ++u) {
if (!used[u] && (v == -1 || mincost[u] < mincost[v]))
v = u;
}
if (v == -1) break; //结束

used[v] = true;
res += mincost[v];
for (int u = 0; u < V; ++u) {
mincost[u] = min(mincost[u], cost[v][u]);
}
}
return res;
}

int main() {
while (~scanf(" %d", &V)) {
for (int i = 0; i < V; ++i) fill(cost[i], cost[i]+V, INF);
for (int i = 0; i < V; ++i) {
for (int j = 0; j < V; ++j) {
scanf(" %d", &cost[i][j]);
}
}//cin
printf("%d\n", prim());
}
return 0;
}


还有Kruskal

/*
* 从小到大查看边,如果不产生环,加把这条边加进生成树中
*/

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <climits>
using namespace std;

const int MAX = 110;
const int INF = INT_MAX>>2;

struct edge { int u, v, cost; };

edge es[MAX*MAX];
int V, E;
int father[MAX];

/* 并查集 */
int find(int x) {
if (father[x] == x) return x;
return father[x] = find(father[x]);
}

bool same(int x, int y) {
return find(x) == find(y);
}

void unite(int x, int y) {
int f1 = find(x), f2 = find(y);
if (f1 == f2) return;
//rank is not necessary
father[f1] = f2;
}

void init_set(int max_f) {
for (register int i = 0; i < max_f; ++i) father[i] = i;
}

bool cmp(edge e1, edge e2) {
return e1.cost < e2.cost;
}

/* Kruskal */
int kruskal(void) {
sort(es, es+E, cmp);
init_set(V);
int res = 0;
for (int i = 0; i < E; ++i) {
edge e = es[i];
if (!same(e.u, e.v)) {
unite(e.u, e.v);
res += e.cost;
}
}
return res;
}

int main() {
while (~scanf(" %d", &V)) {
E = 0;
int cost;
for (int i = 0; i < V; ++i) {
for (int j = 0; j < V; ++j) {
scanf(" %d", &cost);
if (j > i) continue;
es[E].u = i, es[E].v = j, es[E].cost = cost;++E;
//es[++E].u = j, es[E].v = i, es[E].cost = cost;
}
}
printf("%d\n", kruskal());
}
return 0;
}


Prim算法和Djistera在求最短路的时候的过程类似,是从某个顶点出发,然后不断加边的过程.当然,Djistera求最短路是找d值最小的顶点,而Prim则是找cost最小的边.

Kruskal算法每次从剩余边中取出最短的,加到集合X中(初始时集合为空,然后不断加边),但是因为不能取到那种成环的边,举个例子a-b,b-a两条边,那如果a-b已经加进去了,就不能再加b-a了.事实上也是如此,如果最终集合X中有环,我们显然可以把这个环断开,取出环上的某条边,剩下的点依然连通,从而说明如果最后X集合中的边存在环,就一定不是最小生成树了.所以不能加进环,那怎么判断我待选取的边是不是和之前的边构成环呢?那自然就可以用并查集,V个节点,每加一条边ab,我们就合并节点a,b,这样在整个并查集看来,这a,b同"父亲"(father[a]
= father[b]),他们就好像一个点一样.比如有4个点的这个图:1-2, 1-3, 1-4, 2-3, 2-4 假设我们把1-2,1-3,1-4加进去了,他们在并查集中就像一个点一样(不管怎么合并,此时一定有f[1] = f[2] = f[3] = f[4])我们判断2-3该不该加时,由于f[2] = f[3],我们就可以知道,当前加进去的边已经可以让2和3连通了,所以就跳过找下一条边了~~~Kruskal算法总体上比Prim优秀,因为不需要堆优化就已经有O(|E|*log|V|)的复杂度了,而前者没有优化前是O(|V|^2)的.相比较之下,前者代码量又会小一些,所以怎么取舍,自己看吧...
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: