您的位置:首页 > 其它

布线问题(nyoj 38) 最小生成树

2014-02-10 21:54 471 查看

布线问题

时间限制:1000 ms | 内存限制:65535 KB
难度:4
描述 南阳理工学院要进行用电线路改造,现在校长要求设计师设计出一种布线方式,该布线方式需要满足以下条件:

1、把所有的楼都供上电。

2、所用电线花费最少

输入第一行是一个整数n表示有n组测试数据。(n<5)

每组测试数据的第一行是两个整数v,e.

v表示学校里楼的总个数(v<=500)

随后的e行里,每行有三个整数a,b,c表示a与b之间如果建铺设线路花费为c(c<=100)。(哪两栋楼间如果没有指明花费,则表示这两栋楼直接连通需要费用太大或者不可能连通)

随后的1行里,有v个整数,其中第i个数表示从第i号楼接线到外界供电设施所需要的费用。( 0<e<v*(v-1)/2 )

(楼的编号从1开始),由于安全问题,只能选择一个楼连接到外界供电设备。

数据保证至少存在一种方案满足要求。输出每组测试数据输出一个正整数,表示铺设满足校长要求的线路的最小花费。样例输入
1
4 6
1 2 10
2 3 10
3 1 10
1 4 1
2 4 1
3 4 1
1 3 5 6

样例输出
4


最小生成树问题,可以用克鲁斯卡尔算法和普利姆算法,Prim最主要的思想是根据顶点来得出结果,而Kruscal则是根据边来得出结果的,因此Prim最要运用于稠密图的计算,而Kruskal主要是运用于稀疏图的计算。因为只能选择一个楼连接到外界供电设备,所以只要构成最小生成树的边的和加上与外界连接花费最小的值就可以。

Kruskal:算法第一步是给所有边按从小到大排序

第二步初始化最小生成树为空,(初始化连通分量,每个点自成一个独立的连通分量,所以初始状态有多少顶点就有多少连通分量)

第三步按照排列好的顺序,如果两条边不在同一个连通分量,将这两条边合并为同一个连通分量;如果两条边在同一个连通分量里,那么如果再进行连通处理会形成环,所以不能选择。

第四步最小生成树的边的个数是顶点个数减一,可以通过这个来判断生成树是否正确。

2014-3-16我发现自己之前写的Kruscal不对= =…顺便把prim也贴上来

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct node
{
int x, y;
int w;
};

node school[150000];//数组不够大= =
int father[510];//用来记录连通分量,如果此数组中所对应的值相等,说明属于同一个连通分量
int ans;
int v, e;
int s[510];

int cmp (const void *a, const void *b)
{
return (((node *)a)->w - ((node *)b)->w);
}

int cmp2(const void *a, const void *b)
{
return (*(int *)a - *(int *)b);
}

void Kruscal()
{
qsort(school, e, sizeof(school[0]), cmp);
int p, q, j, i;
int l = 0, num = 1;
while(num < v)//保证最小生成树的边的个数是顶点个数减一
{
p = school[l].x;
q = school[l].y;
if(father[q] != father[p])//不在同一个连通分量里
{
num ++;
ans += school[l].w;
//printf("ans = %d\n", ans);
int t = father[q];//注意这里
for(j = 1; j <= v; j++)
{
if(father[j] == t)
{
father[j] = father[p];
//将father对应的数组值改成相等的,这就标志着q所在的连通分量已经与p所在的连通分量合并
}
}
}
l++;
}
}

int main (void)
{
int n, i;
scanf("%d", &n);
while(n --)
{
scanf("%d %d", &v, &e);
for(i = 0; i < e; i++)
{
scanf("%d %d %d", &school[i].x, &school[i].y, &school[i].w);
}
for(i = 1; i <= v; i++)
{
father[i] = i;
scanf("%d", &s[i]);
}
qsort(&s[1], v, sizeof(s[1]), cmp2);
Kruscal();

printf("%d\n", ans + s[1]);
ans = 0;
}
return 0;
}


对于样例数据,father数组的变化情况如下:

p = 2, q = 4

1 2 3 2 将2和4数组值置为相同,标志着2和4已经连通

p = 1, q = 4

1 1 3 2

p = 1, q = 4 将1和4所在的连通分量中的所有点的值都置成相同的值

1 1 3 1

p = 3, q = 4

3 1 3 1

p = 3, q = 4

3 3 3 1

p = 3, q = 4 最后所有的点的值都会变成一样,说明全部在一个连通分量中

3 3 3 3

prim:

#include <stdio.h>
#include <string.h>
int map[510][510];
int ans;
bool vis[510];
int des[510];

void init(int v)
{
int i, j;
for(i = 0; i <= v; i++)
{
for(j = 0; j <= v; j++)
map[i][j] = 999999999;
}
}

void prim(int v, int e)
{
int i, j;
memset(vis, 0, sizeof(vis));
for(i = 1; i <= v; i++)
{
des[i] = map[1][i];
}

vis[1] = 1;
des[1] = 0;
int num = 1;
while(num < v)
{
int min = 999999999, k;
for(i = 2; i <= v; i++)
{
if(des[i] < min && vis[i] == 0)
{
min = des[i];
k = i;
}
}
ans += min;
vis[k] = 1;
num++;
for(i = 2; i <= v; i++)
{
if(map[k][i] < des[i] && vis[i] == 0)
des[i] = map[k][i];
}
}
}

int main (void)
{
int n;
int v, e;
scanf("%d", &n);
while(n --)
{
ans = 0;
scanf("%d %d", &v, &e);
init(v);
int i, a, b, c;
for(i = 0; i < e; i++)
{
scanf("%d %d %d", &a, &b, &c);
if(map[a][b] > c)
map[a][b] = map[b][a] = c;
}
int t = 999999999;
for(i = 0; i < v; i++)
{
scanf("%d", &a);
if(t > a)
t = a;
}
prim(v, e);

printf("%d\n", ans + t);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: