【题目描述】
如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出 orz
。
数据规模:
对于 20% 的数据, M≤20。
对于 40% 的数据, M≤2500。
对于 70% 的数据, M≤10^4。
对于 100% 的数据: 50001≤N≤5000, 1≤M≤2×10^5, 10^41≤Zi≤。
【输入】
第一行包含两个整数 N,M,表示该图共有 N 个结点和 M 条无向边。
接下来 M 行每行包含三个整数 Xi,Yi,Zi,表示有一条长度为 Zi 的无向边连接结点 Xi,Yi。
【输出】
如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 orz
。
样例输入
4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3
样例输出
7
解题思路
这个题可以用 Prim 和 Kruskal 解题,首先讲讲 Prim 算法。
它是以某一个顶点为起点,遍历周围可以到达的顶点,从中找到两顶点的最小权值 min,找到后将最小权值加入——初始化后的 sum,并且将当前的最小权值最小权值赋为 0(因为后面接着要找最小权值,如果去掉这个 min 的值,那么第二轮循环以及后面的循环找到的最小权值不准确)。
这个题我们将下标为 1 的顶点设为起点,定义数组 lowcost 存储当前从起点,到以该下标为顶点的最小路径,定义数组 adjvex 存储它当前连接的顶点(比如 adjvex [3]=4,代表顶点 3 连接着顶点 4)。
之前想过为什么要额外把顶点 1 连接的权值存入 lowcost 数组,难道不可以直接用循环从 i=1 到 i=n吗?事实是也可以,不过在之前要把 lowcost 数组的值初始化为一个较大值。
代码如下:
#include<stdio.h>
int flag,sum,n,m,a[5005][5005];
void MiniSpanTree_Prim()
{
int i,j,k,min;
int adjvex[5005];//存储顶点间边的权值下标
int lowcost[5005];//存储当前权值(如果值为 0,说明已经经过该点)
lowcost[1]=0;//表示以顶点 1为起点,将顶点 1加入生成树
adjvex[1]=1;//因为顶点 1是起点,它的上端没有连接,所以将值赋为 1
for(i=2;i<=n;i++)//初始化,将起点与各顶点的权值存入,如果不可到达,就赋为一个较大值
{
lowcost[i]=a[1][i];
adjvex[i]=1;//此时这些顶点的上端都是顶点 1
}
for(i=2;i<=n;i++)
{
min=99999999;//将 min赋为一个较大值
k=1;
for(j=1;j<=n;j++)
{
if(lowcost[j]<min&&lowcost[j]!=0)//首先保证该顶点没有重复
{
min=lowcost[j];//找最小值
k=j;//记录下标
}
}
if(min<99999999)
{
sum=sum+min;
lowcost[k]=0;//加上权值后,将它赋为 0,否则会出错
}
//如果不可以通过,也就是 min==99999999, 说明还有顶点未加入生成树,此时结束函数,并用 flag标记,结尾输出 orz
else
{
flag=1;
return ;
}
for(j=2;j<=n;j++)
{
if(lowcost[j]!=0&&a[k][j]<lowcost[j])
{
lowcost[j]=a[k][j];
adjvex[j]=k;
}
}
}
}
int main()
{
int i,j,x,y,z;
scanf("%d %d",&n,&m);
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
a[i][j]=99999999;//初始化
}
for(i=0;i<m;i++)
{
scanf("%d %d %d",&x,&y,&z);
if(z<a[x][y])
{
a[x][y]=z;//不要忘了是双向图
a[y][x]=z;
}
}
MiniSpanTree_Prim();
if(flag==1)
printf("orz");
else
printf("%d",sum);
return 0;
}