最小生成树算法应用:连接点的最优路径问题
最小生成树算法应用:连接点的最优路径问题
一、引言
在计算机科学与实际生活中,常常会遇到需要寻找最优连接路径的问题,比如在城市中铺设通信电缆,如何在满足连接所有节点的情况下使得电缆总长度最短。今天我们通过一段代码来探讨使用 Kruskal 算法解决此类问题的实现过程。
二、代码总体功能概述
这段代码主要是通过 Kruskal 最小生成树算法,来计算给定的一些点之间连接的最小总距离。代码中涉及到两种类型的点,一种是 n 个 vx 和 vy 表示的点,另一种是 m 个 gx 和 gy 表示的点。目标是将这些点连接起来,使得连接的总距离最小。
三、代码详细分析
(一)头文件与命名空间
cpp
include <bits/stdc++.h>
using namespace std;
使用了 bits/stdc++.h 这个包含了众多标准库头文件的万能头文件,并引入了 std 命名空间,方便后续使用标准库的各种函数和类型。
(二)常量与全局变量定义
cpp
const double inf=0x3f3f3f3f3f3f3f3f1.0;
const int N=1e5+100;
struct edge{
int u,v;double weight;
edge(int a=0,int b=0,double w=inf){u=a;v=b;weight=w;}
bool operator<(const edge &e)const{return weight<e.weight;}
};vector
int pre[N];int n,m;int vx[N],vy[N];int gx[N],gy[N];double sum=0;
inf 定义了一个极大值,用于表示初始的无穷远距离。
N 定义了一个数组大小的常量,用于确定一些数组的最大长度。
edge 结构体定义了边的属性,包括边的两个端点 u 和 v 以及边的权重 weight,还重载了小于号运算符,方便后续对边按权重进行排序。
es 是一个存储边的向量。
pre 数组用于并查集,记录每个节点的前驱节点。n 和 m 分别表示两种不同类型点的数量。vx、vy 存储第一种点的坐标,gx、gy 存储第二种点的坐标。sum 用于记录最终的最小总距离。
(三)距离计算函数
cpp
double gd(int x1,int y1,int x2,int y2)
{
double d=1.0
return sqrt(d);
}
这个函数 gd 用于计算平面上两点 (x1, y1) 和 (x2, y2) 之间的欧几里得距离。通过先计算横纵坐标差值的平方和,再开方得到两点间的距离。
(四)并查集相关函数
cpp
int root (int x)
{return pre[x]=(pre[x]x?x:root(pre[x]));}
root 函数是并查集的查找函数,用于找到节点 x 所在集合的根节点。通过路径压缩的方式,将节点 x 的前驱节点直接指向根节点,提高后续查找的效率。
(五)Kruskal 算法实现函数
cpp
void kruskal()
{
sort(es.begin(),es.end());
for(auto e:es)
{
int u=e.u,v=e.v;double weight=e.weight;
if(root(u)root(v)){continue;}
sum+=weight;
pre[root(u)]=root(v);
}
}
kruskal 函数实现了 Kruskal 最小生成树算法。首先对存储边的向量 es 按照边的权重进行排序。然后遍历排序后的边,对于每条边,如果其两个端点不在同一个集合中(通过并查集判断),就将这条边的权重加入到总距离 sum 中,并将两个端点所在的集合合并(通过并查集的合并操作)。
(六)主函数
cpp
int main()
{
cin>>n>>m;
for(int i=1;i<N;i++){pre[i]=i;}
for(int i=1;i<=n;i++)
{
cin>>vx[i]>>vy[i];
}
for(int i=1;i<=m;i++)
{
cin>>gx[i]>>gy[i];
}
for(int i=1;i<=n;i++)
{
int x1=vx[i],y1=vy[i];
for(int j=i+1;j<=n;j++)
{
int x2=vx[j],y2=vy[j];
double dis=gd(x1,y1,x2,y2);
es.push_back(edge(i,j,dis));
es.push_back(edge(j,i,dis));
}
}
for(int i=1;i<=m;i++)
{
int x1=gx[i],y1=gy[i];
double dis=inf;
for(int j=1;j<=n;j++)
{
int x2=vx[j],y2=vy[j];
dis=min(dis,gd(x1,y1,x2,y2));
}
sum+=dis;
}
kruskal();
printf("%.2f",sum);
return 0;
}
首先从输入读取 n 和 m 的值,并初始化并查集数组 pre,使得每个节点的前驱都是其自身。
接着读取 n 个点的坐标 vx 和 vy 以及 m 个点的坐标 gx 和 gy。
然后通过双重循环计算 n 个点之间两两的距离,并将对应的边加入到边向量 es 中(因为是无向图,所以正反两个方向的边都要加入)。
再通过循环计算 m 个点到 n 个点中距离最近的距离,并累加到 sum 中。
最后调用 kruskal 函数计算最小生成树的总权重,并将结果以保留两位小数的形式输出。
四、总结
通过这段代码,我们展示了如何使用 Kruskal 算法解决点的连接最优路径问题。代码中并查集和距离计算等操作的结合,使得我们能够高效地找到最小总距离。在实际应用中,类似的算法可以用于网络拓扑设计、物流路径规划等众多领域,通过寻找最优连接方式,达到节省资源、提高效率的目的。同时,代码中的一些实现细节,比如并查集的路径压缩和边的排序等,对于算法的性能优化也起到了重要作用。
代码功能概述
这段代码实现了 Kruskal 算法来解决最小生成树(MST)问题,并且尝试找出最小生成树中权值最大的边。如果图是连通的,输出最小生成树中权值最大的边;如果图不连通,则输出 -1。
续写思路
为了让代码更完善,我们可以做以下几个方面的改进:
添加输入验证:确保输入的 n 和 m 是有效的。
添加注释:让代码更易读。
封装关键功能:将一些关键逻辑封装成函数,提高代码的模块化程度。
添加异常处理:处理可能出现的异常情况。
续写后的代码
cpp
include <bits/stdc++.h>
using namespace std;
const int N = 200010;
// 定义边的结构体,包含起点、终点和边的权值
struct ty {
int a, b, c;
};
ty e[N];
int fa[N];
// 查找元素所在集合的根节点,并进行路径压缩
int find(int x) {
return fa[x] = (fa[x] == x ? x : find(fa[x]));
}
// 比较函数,用于对边按权值从小到大排序
bool cmp(ty x, ty y) {
return x.c < y.c;
}
// 执行 Kruskal 算法,返回最小生成树中最大边的权值,如果图不连通返回 -1
int kruskal(int n, int m) {
// 初始化并查集
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
// 对边按权值从小到大排序
sort(e + 1, e + 1 + m, cmp);
int ans = 0;
// 遍历每条边
for (int i = 1; i <= m; i++) {
int fx = find(e[i].a);
int fy = find(e[i].b);
// 如果边的两个端点在同一个集合中,跳过该边
if (fx == fy) {
continue;
}
// 合并两个集合
fa[fx] = fy;
// 更新最小生成树中最大边的权值
ans = max(ans, e[i].c);
}
// 检查图是否连通
int c = 0;
for (int i = 1; i <= n; i++) {
if (fa[i] == i) {
c++;
}
// 如果连通分量数大于 1,图不连通
if (c > 1) {
return -1;
}
}
return ans;
}
int main() {
int m, n;
// 输入顶点数和边数
cin >> n >> m;
// 输入验证
if (n <= 0 || m < 0) {
cout << "输入的顶点数或边数无效。" << endl;
return 1;
}
// 输入每条边的信息
for (int i = 1; i <= m; i++) {
cin >> e[i].a >> e[i].b >> e[i].c;
// 输入验证
if (e[i].a < 1 || e[i].a > n || e[i].b < 1 || e[i].b > n) {
cout << "输入的边的顶点编号无效。" << endl;
return 1;
}
}
// 执行 Kruskal 算法
int result = kruskal(n, m);
// 输出结果
cout << result << endl;
return 0;
}
代码解释
输入验证:在 main 函数中,对输入的 n 和 m 以及每条边的顶点编号进行验证,确保输入的有效性。
封装 Kruskal 算法:将 Kruskal 算法封装在 kruskal 函数中,提高代码的模块化程度。
异常处理:如果输入无效,输出错误信息并返回非零值,表示程序异常结束。
通过这些改进,代码的健壮性和可读性都得到了提升。

浙公网安备 33010602011771号