题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=38
题目分析:
其实就是求图的最小生成树。有两种方法。prim法和kruskal法。prim法只与节点有关,与边无关,所以适合于求边稠密的网的最小生成树。而kruskal算法与边有关,故其适合于求边比较稀疏的网络。
prim算法:
1)初始化set集为随意的一个节点,这里初始化为1。
2)找出与set集中的点相连的,花费最小的并且不再set集中的点,加入set集中。为了计算的方便,先将每个节点相连的所有边按权值升序排列。
3)直到所有的点都加到set集中,算法就停止了。
kruskal算法:
1)每次找权值最小的边,如果节点没有访问过,就加到set集中。如果访问过了,就合并两个set集。
2)这里为了剪去不必要的迭代,如果连通区域为1,并且所有的点都已经访问过了,就退出。
参考代码:
prim算法的代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct NODE
{
int to;
int w;
};
NODE Map[501][501];//Map[i][0].to存放节点i相邻的点的个数
bool used[501];
int set[501];
int compare(const void *a, const void *b)
{
NODE *p1 = (NODE *)a;
NODE *p2 = (NODE *)b;
return p1->w - p2->w;
}
void GetMap(int n)
{
for(int i = 1; i <= n; ++i)
qsort(&Map[i][1], Map[i][0].to, sizeof(Map[i][0]), compare);
}
int Prim(int n)
{
int num = 1;//set集合内点的个数
int i,j;
int ans = 0;
NODE temp;
set[0] = 1;
used[1] = true;
while(num < n)
{
temp.to = -1;
temp.w = 101;
for(i = 0; i < num; ++i)
{
for(j = 1; j <= Map[set[i]][0].to; ++j)
{
if(!used[Map[set[i]][j].to])
{
if(Map[set[i]][j].w < temp.w)
temp = Map[set[i]][j];
break;
}
}//end for j
}//end for i
if(temp.to != -1)
{
ans += temp.w;
set[num++] = temp.to;
used[temp.to] = true;
}
}//end for while
return ans;
}
int main()
{
int t,i;
int v,e;
int a,b,c;
int ans;
scanf("%d", &t);
while(t--)
{
memset(used,0,sizeof(used));
scanf("%d %d", &v, &e);
for(i = 0; i <= v; ++i)
Map[i][0].to = 0;
for(i = 0; i < e; ++i)
{
scanf("%d %d %d", &a, &b, &c);
++(Map[a][0].to);
++(Map[b][0].to);
Map[a][Map[a][0].to].to = b;
Map[a][Map[a][0].to].w = c;
Map[b][Map[b][0].to].to = a;
Map[b][Map[b][0].to].w = c;
}
scanf("%d", &b);
for(i = 1; i < v; ++i)
{
scanf("%d", &a);
b = b < a ? b : a;
}
GetMap(v);
ans = Prim(v);
ans += b;
printf("%d\n", ans);
}
return 0;
}
kruskal算法的代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct EDGE
{
int from;
int to;
int w;
};
EDGE edge[124760];//所有的边
bool used[501];
int set[501];
int compare(const void *a, const void *b)
{
EDGE *p1 = (EDGE *)a;
EDGE *p2 = (EDGE *)b;
return p1->w - p2->w;
}
int find(int k)
{
int r = set[k];
while(r != set[r])
r = set[r];
return r;
}
void Merge(int r1, int r2)
{
if(r1 < r2)
set[r2] = r1;
else
set[r1] = r2;
}
int Kruskal(int v, int e)
{
int ans = 0;
int t, num;//t为连通区域的个数,num为已访问的节点的个数
int r1, r2;
t = num = 0;
while(num != v && t != 1)
{
for(int i = 0; i < e; ++i)
{
if(!used[edge[i].from])
{
++t;
++num;
used[edge[i].from] = true;
}
if(!used[edge[i].to])
{
++t;
++num;
used[edge[i].to] = true;
}
r1 = find(edge[i].from);
r2 = find(edge[i].to);
if(r1 != r2)
{
--t;
Merge(r1, r2);
ans += edge[i].w;
}
}//end for i
}//end while
return ans;
}
int main()
{
int t,i;
int v,e;
int a,b,c;
int ans;
scanf("%d", &t);
while(t--)
{
memset(used,0,sizeof(used));
scanf("%d %d", &v, &e);
for(i = 1; i <= v; ++i)
set[i] = i;
for(i = 0; i < e; ++i)
{
scanf("%d %d %d", &a, &b, &c);
edge[i].from = a;
edge[i].to = b;
edge[i].w = c;
}
qsort(edge, e, sizeof(edge[0]), compare);
scanf("%d", &b);
for(i = 1; i < v; ++i)
{
scanf("%d", &a);
b = b < a ? b : a;
}
ans = Kruskal(v, e);
ans += b;
printf("%d\n", ans);
}
return 0;
}
由于prim方法针对节点,而kruskal方法针对边,所以二者的数据结构有点不一样。