定义
- 连通网: 在连通图中,若图的边具有一定的意义,每一条边都对应着一个数,称为权;权代表着连接连个顶点的代价,称这种连通图叫做连通网。
- 生成树: 一个连通图的生成树是指一个连通子图,它含有图中全部n个顶点,但只有足以构成一棵树的n-1条边。一颗有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环。
- 最小生成树: 在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树。
接下来以题为例,分别介绍prim和kruskal算法
关于输入输出的要求:
输入一个n表示总共的村庄数
接下来输入一个n*n的矩阵表示铺设电话线的路径
(第一行的n个数分别表示a与b,c,d,e,f直接连通的路径,以此类推其他行)
输出就是最小的电话线铺设路径
输入样例
6
0 3 0 5 4 0
3 0 1 0 0 8
0 1 0 3 0 4
5 0 3 0 11 0
4 0 0 11 0 6
0 8 4 0 6 0
输出样例
15
prim算法
假设初始时,只有一个点是连接的,从这个点开始顺次查找连接外面最小花费的边(用于稠密图)
#include<iostream>
#include<cstdio>
using namespace std;
const int INF = 1e8;
int a[55][55]; //a[i][j]表示从i到j的路径长度
int dist[55]; //已连接的点中,到下标index最小的路径
int vis[55];
long long ans;
int main()
{
int i, j, k, n, mark;
scanf("%d", &n);
for (i = 1; i <= n; i++)
{
for (j = 1; j <= n; j++)
{
scanf("%d", &a[i][j]);
if (a[i][j] == 0) a[i][j] = INF; //等于0表示不连通,路径为无穷,INF是预设的很大的值
}
}
//下面是prim算法
dist[1] = 0; //从V1开始顺次连接,V1到V1,路径为0
vis[1] = 1; //vis为1表示已经连接(可以通话了)
for (i = 2; i <= n; i++)
{
dist[i] = a[1][i]; //初始的时候只有1是连接的,所以到i村庄最小的造价就是原数组a[1]的值
}
dist[0] = INF; //让dist[0]最大,方便后面找到较小的路径
for (i = 1; i <= n; i++)
{
mark = 0;
for (j = 1; j <= n; j++) //依次遍历,找到最小的
{
if (vis[j]==0&&dist[mark] > dist[j]) mark = j; //标记目前路径最短的村庄
}
if (mark == 0) break; //如果mark没变,说明不存在连通的村庄了,退出循环
ans += dist[mark]; //ans存储总路径,dist[mark]是当前最短的路径,加上去
vis[mark] = 1; //第mark个村庄可以通话了
for (j = 1; j <= n; j++) //因为可以通话的村庄范围变了,需要重新更新dist的值
{
if (a[mark][j] < dist[j]) dist[j] = a[mark][j]; //从mark到j的路径如果比原来的小,更新
}
}
printf("%d\n", ans); //输出结果
}
kruskal算法
假设初始时各个点都是单独的孤岛,路径从小到大依次连接连通分支(用于稀疏图)
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
struct Edge {
int x, y, p; //分别表示边的左右顶点和权值
}E[2222];
int fa[55];
int Emax, num; //Emax表示可搭建的边数,num表示已搭建的边数
long long ans;
int find(int x)
{
if (x != fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
void unity(int a, int b)
{
a = find(a);
b = find(b);
fa[a] = b;
}
int cmp(const struct Edge& a, const struct Edge& b)
{
return a.p < b.p;
}
void build(int i, int j, int temp)
{
Emax++;
E[Emax].x = i;
E[Emax].y = j;
E[Emax].p = temp;
}
int main()
{
int i, j, k, n, temp;
scanf("%d", &n);
for (i = 1; i <= n; i++)
{
fa[i] = i;
}
for (i = 1; i <= n; i++)
{
for (j = 1; j <= n; j++)
{
scanf("%d", &temp);
if (temp != 0) build(i, j, temp); //如果有边,则构造这样的边
}
}
sort(E, E + Emax, cmp); //kruskal算法需要按权值从小到大排序
i = 1;
while (num < n - 1) //n个村庄总共搭建n-1条边
{
while (i <= Emax && find(E[i].x) == find(E[i].y)) i++; //找到第一个不在一个连通分支内的点
if (i > Emax) break; //如果i>Emax,说明所有村庄已经可以通话了,退出循环
ans += E[i].p; //这是一条需要搭建的边,加上它
unity(E[i].x, E[i].y); //合并为一个连通分支
num++; //边的条数+1
}
printf("%d\n", ans);
}
前面kruskal算法的代码有点丑
下面给出相对好一点的模板
模板参考:最小生成树详解+经典例题
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct node{
int x,y;//树一条边的起点和终点;
int w;// 边权(边的价值或大小)
}a[110];
int pre[110];//每个点的信息; 并查集所用;
bool cmp(struct node a,struct node b)
{
return a.w<b.w;
}
int find(int root)// 查找函数(并查集)
{
if(root!=pre[root]) root=find(pre[root]);
return pre[root];
}
int main()
{
int n,i,m;
while(scanf("%d %d",&n,&m),n)
{
memset(pre,0,sizeof(pre));
int sum=0,num=0;
for(i=1;i<=m;i++) scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].w);//输入各个边的起点、终点和权值;
sort(a+1,a+1+m,cmp);// 对各个边进行排序;
for(i=1;i<=n;i++) pre[i]=i;
for(i=1;i<=m;i++)// 从小到大开始枚举测试边;用并查集判断是否构成环; 不构成则加入;
{
int f1=find(a[i].x);
int f2=find(a[i].y);
if(f1!=f2)
{
pre[f1]=f2;
sum+=a[i].w;//存储最小的边权和;
num++;
}
if(num==n-1) break;// 满足树的性质; 边的条数==点的个数-1;
}
if(num==n-1)//判断是否存在;
printf("%d\n",sum);
else printf("?\n");
}
return 0;
}