在一张带权的无向连通图中,各边权和最小的一颗生成树即为最小生成树。
在现实生活中,很多布线问题,通线网络等问题,都可以直接或者间接的转化成最小生成树的问题去给出花费最小的方案。
计算最小生成树可以采用两种方式,即Kruscarl算法和Prim算法。这两种算法都采用了贪心策略。
看过的一本书上解释说,这两种方法实际上是“殊途同归”的:“同归”指的是两种算法都采用了扩展安全边的贪心策略,“殊途”指的是两种算法扩展安全边的方式和应用场合各不相同。
下面的图是一个无向带权连通图,构建最小生成树前先任意选择一个顶点(我们假设选v1),从选择的顶点开始构建。
注:出发点不同,最小生成树的形态就不同,但边权和的最小值是唯一的。
Kruskal算法求解最小生成树过程:初始时,森林是由单个结点组成的n棵树。然后反复找出森林中连接任意两棵树的所有边中具有最小权值的边(u , v),将其作为安全边,把它添加到正在生长的森林中,直至产生最小生成树为止。
prim算法求解最小生成树过程:prim算法执行过程中,集合A中的边总是只形成单棵树。初始时,A为空。接下来每次添加到A的边都是使树的权尽可能小的边。这个过程一直进行到不存在连接生成树的边为止。
描述
南阳理工学院要进行用电线路改造,现在校长要求设计师设计出一种布线方式,该布线方式需要满足以下条件:
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
该题是最小生成树的一个应用。可以很明显的看出来,要想花费最小,就必须要在各个楼中建立一个最小的生成树。在建立最小生成树的时候,不要将楼与外界供电设备的这条线加入到建树的过程中,直接在连通图上建树,到最后直接加上一个与外界供电设备连接费用最少的线路就可以。
这道题我用两种方式都AC了,两种方法各有优势,具体情况看下面的表格对比
| 结果 | 时间 | 内存 | 语言| 方法|
| ------------- |:------------- -----
| Accepted | 144 | 9648 |C/C++|Prim|
| Accepted | 150 | 11640 |C/C++|Kruskal|
AC代码1——kruskal算法
#include
#include
using namespace std;
const int N = 505;
typedef struct Edge{
int from , to ,cost;
}edge;
int n;
int v , e;
int father[N];
edge edg[N * N];
int start[N];
int ans;
//并查集
int find(int a){
int x = a;
while(father[x] != x){
x = father[x];
}
int i = a , j;
while(father[i] != x){
j = father[i];
father[i] = x;
i = j;
}
return x;
}
void join(int a , int b){
int father1 = find(a);
int father2 = find(b);
if(father1 != father2){
father[b] = a;
}
}
bool cmp(edge e1 , edge e2){
return e1.cost < e2.cost;
}
void kruskal(){
for(int i = 0;i < e;i++){
int f1 = find(edg[i].from);
int f2 = find(edg[i].to);
if(f1 != f2){
join(father[edg[i].from] , father[edg[i].to]);
ans += edg[i].cost;
}
}
}
int main(){
cin >> n;
while(n--){
ans = 0;
cin >> v >> e;
for(int i = 1;i < N;i++){
father[i] = i;
}
for(int i = 0;i < e;i++){
cin >> edg[i].from >> edg[i].to >> edg[i].cost;
}
for(int i = 0;i < v;i++){
cin >> start[i];
}
sort(edg , edg + e , cmp);
sort(start , start + v);
kruskal();
cout << ans + start[0] << endl;
// cout << ans << endl;
// for(int i = 0;i < e;i++){
// cout << edg[i].from <<" "<< edg[i].to << " " << edg[i].cost << endl;
// }
}
return 0;
}
AC代码2——Prim算法
#include
#include
#include
#include
using namespace std;
#define MAXN 505
#define INF 0x3f3f3f3f
int closest[MAXN] , lowcost[MAXN];
int G[MAXN][MAXN];//邻接矩阵
int n , v , e;
int start[MAXN];
int prim(){
for(int i = 0 ; i <= v ; i++){
lowcost[i] = INF;
}
for(int i = 0 ; i <= v ; i++){
closest[i] = 0;
}
closest[1] = -1; //加入第一个点
int num = 0 , ans = 0 , new_point = 1; //new_point为最新加入集合的点
while(num < v - 1){ //加入v-1条边
int micost = INF , miedge = -1;
for(int i = 1;i <= v;i++){
if(closest[i] != -1){
int temp = G[new_point][i];
if(temp < lowcost[i]){
lowcost[i] = temp;
closest[i] = new_point;
}
if(lowcost[i] < micost){
micost = lowcost[miedge = i];
}
}
}
ans += micost;
closest[new_point = miedge] = -1;
num++;
}
return ans;
}
int main(){
cin >> n;
while(n--){
cin >> v >> e;
int from , to , cost;
memset(G , 0x3f , sizeof(G));
for(int i = 1 ; i <= e;i++){
cin >> from >> to >> cost ;
G[from][to] = cost;
G[to][from] = cost;
}
for(int i = 0;i < v;i++){
cin >> start[i];
}
sort(start , start + v);
cout << prim() + start[0] << endl;
}
return 0;
}