题目描述
- 给定一个
n
个点m
条边的无向图,图中可能存在重边和自环,边权可能为负数。 - 求最小生成树的树边权重之和,如果最小生成树不存在则输出
impossible
。 - 给定一张边带权的无向图
G=(V,E)
,其中V
表示图中点的集合,E
表示图中边的集合,n=|V|
,m=|E|
。由V
中的全部n
个顶点和E
中n−1
条边构成的无向连通子图被称为G
的一棵生成树,其中边的权值之和最小的生成树被称为无向图G
的最小生成树。
输入格式
- 第一行包含两个整数
n
和m
。 - 接下来
m
行,每行包含三个整数u
,v
,w
,表示点u和点v之间存在一条权值为w
的边。
输出格式
- 共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出
impossible
。
数据范围
1 ≤ n ≤ 500
,1 ≤ m ≤ 10^5
,- 图中涉及边的边权的绝对值均不超过
10000
。
基本思路
- Prim算法的步骤如下:
- 步骤1:选一个起点。首先,我们从图中选择一个顶点作为起点,这个顶点可以任意选择。
- 步骤2:把起点加入集合。将选定的起点加入到最小生成树的集合中。
- 步骤3:寻找与集合最近的顶点。在集合外的所有顶点中,寻找与集合最近的顶点。这个“最近”是指与集合中某个顶点相连的边的权重最小。
- 步骤4:将最近的顶点加入集合。将找到的最近顶点加入到最小生成树的集合中,同时将连接这个顶点与集合的边也加入到最小生成树中。
- 步骤5:重复步骤3和步骤4 重复步骤3和步骤4,直到所有顶点都加入到最小生成树的集合中。如果执行步骤3的过程中集合外的所有顶点道集合的距离都是无穷大(也就是集合外的点和集合内的点不连通),则说明不存在生成树。
实现代码
#include <cstdio>
#include <climits>
#include <algorithm>
using namespace std;
// 【变量定义】无向图中点的个数和边的条数
int n, m;
// 【变量定义】无向边的两个端点编号和边长(权重)
int u, v, w;
// 【辅助常量定义】无向图中的点个数上限(多5防止溢出)
const int N = 505;
// 【变量定义】用于表示该无向图的邻接矩阵
int adjacent_matrix[N][N];
// 【变量定义】用于记录最小生成树的边权重之和
int weight_sum;
// 【变量定义】用于记录Prim算法中有哪些点在集合中的数组
bool in_set[N];
// 【变量定义】用于记录各个点离Prim算法中的集合的距离的函数
int distances[N];
// 【变量定义】用于记录Prim算法每一轮迭代中的离集合的最短距离和拥有最短距离的点的编号
int min_distance, min_number;
// 【变量定义】用于记录Prim算法中当前进行迭代的点的编号
int current;
// 【函数定义】初始化无向图邻接矩阵的函数
void init_adjacent_matrix(void)
{
// 初始化邻接矩阵:每个点到自身的距离为0,到其他点的距离为无穷大
for(int row = 1; row <= n; ++ row)
for(int col = 1; col <= n; ++ col)
{
if(row != col) adjacent_matrix[row][col] = INT_MAX;
else adjacent_matrix[row][col] = 0;
}
}
// 【函数定义】用于进行Prim算法初始化的函数
void Prim_init(void)
{
// 首先将距离数组中的元素全部设置为正无穷
for(int i = 1; i <= n; ++ i) distances[i] = INT_MAX;
// 将编号为1的点放入集合中,并将其离集合的距离设置为0
in_set[1] = true;
distances[1] = 0;
// 记录当前进行迭代的点是1号点
current = 1;
}
// 【函数定义】基于朴素Prim算法的求最小生成树是否存在的函数
bool Prim(void)
{
// 【初始化】调用辅助函数进行算法初始化
Prim_init();
// 【算法主体】由于每次迭代是将一个点放入集合中,还剩下n-1个点,因此迭代次数是n-1
for(int i = 1; i < n; ++ i)
{
// 【初始化】将初始的最短距离设置为INT_MAX,并将对应的点编号设置为-1
min_distance = INT_MAX;
min_number = -1;
for(int j = 1; j <= n; ++ j)
{
// 【第一步】找出当前不在集合中的点
if(in_set[j] == false)
{
// 【第二步】判定该点离集合的距离是否因为当前的迭代点缩短,如果缩短,则进行更新
if(distances[j] > adjacent_matrix[current][j]) distances[j] = adjacent_matrix[current][j];
// 【第三步】判定该点离集合的距离是否是不在集合中的点的距离的最小值,如果是的话则进行更新并记录
if(distances[j] < min_distance)
{
min_distance = distances[j];
min_number = j;
}
}
}
// 【第四步】判定当前一轮迭代是否不在集合中点与集合中的点之间已经不存在连边,如果不存在则没有生成树
if(min_number == -1) return false;
// 【第五步】将本轮添加的树枝权重增加到总和中,并将该点加入集合,作为下一轮迭代开始的点
weight_sum += min_distance;
in_set[min_number] = true;
current = min_number;
}
// 迭代正常结束,说明存在生成树,返回结果
return true;
}
int main(void)
{
// 【变量输入】输入无向图中点的个数和边的条数
scanf("%d%d", &n, &m);
// 【初始化】通过自定义的函数初始化邻接矩阵
init_adjacent_matrix();
// 【变量输入】输入无向图中的所有无向边(包括端点编号和边长)并进行信息记录
for(int i = 0; i < m; ++ i)
{
scanf("%d%d%d", &u, &v, &w);
// 使用邻接矩阵存储该无向边,注意事项如下:
// 1.对于重边,根据题意只存储边长较短的一条;
// 2.无向边相当于两条反向的有向边,需要存储两次
// 3.需要跳过负权自环(因为加入负权自环无法构成树)
if(u == v && w < 0) continue;
else
{
adjacent_matrix[u][v] = min(w, adjacent_matrix[u][v]);
adjacent_matrix[v][u] = min(w, adjacent_matrix[v][u]);
}
}
// 【判定结果和输出】通过自定义的函数判定该无向图中是否存在生成树
if(Prim() == true) printf("%d", weight_sum);
else printf("impossible");
return 0;
}