使用并查集,数据结构设计上除了要设计存储边,还需依据本题额外设计存储结点坐标的结构,读入每个结点的坐标后,还需要预先计算出每条可能的边的权值以供后面Kruskal选择边;计算每条边时不需要保留两位小数,如此会有精度上的缺失,计算出最终结果后再保留两位小数
#include <cstdio>
#include <cmath>
#include <vector>
#include <algorithm>
using namespace std;
#define N 101
int father[N], height[N];
struct node {
double x, y; // 结点的横纵坐标
} Node[N]; // 结点的结构体数组,从0开始编号
struct Edge {
double s, t; // 边的起始端点和结束端点
double weight;
};
bool cmp(Edge lhs, Edge rhs) {
return lhs.weight < rhs.weight;
}
void Init(int n) {
for (int i = 0; i < n; i++) {
father[i] = i;
height[i] = 1;
}
}
int Find(int x) {
if (x != father[x]) {
father[x] = Find(father[x]);
}
return father[x];
}
void Union(double x, double y, double weight, double &ans) {
int m = Find(x);
int n = Find(y);
if (m != n) {
ans += weight;
}
if (height[m] > height[n]) {
father[n] = m;
}
else if (height[m] < height[n]) {
father[m] = n;
}
else {
father[n] = m; // 将n并入m,以m为根的树高度+1
++height[m];
}
}
// 最小生成树,Kruskal,并查集
int main() {
int n; // 坐标的数量
scanf("%d", &n);
Init(n); // 初始化并查集
double ans = 0, weight;
vector<Edge> graph;
// 读入结点的横纵坐标
for (int i = 0; i < n; i++) {
scanf("%lf%lf", &Node[i].x, &Node[i].y);
}
// 计算n个结点n*(n-1)/2条边的权重,两个结点i和j一条边
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
Edge edge;
edge.s = i;
edge.t = j;
weight = sqrt(pow(abs(Node[i].x - Node[j].x), 2) + pow(abs(Node[i].y - Node[j].y), 2));
edge.weight = weight;
graph.push_back(edge);
}
}
sort(graph.begin(), graph.end(), cmp); // 按边的权值排序
for (int i = 0; i < n * (n - 1) / 2; i++) { // 按从大到小的顺序遍历每条边,选择合适的边加入最小生成树
Union(graph[i].s, graph[i].t, graph[i].weight, ans);
}
printf("%.2f\n", ans);
return 0;
}

京公网安备 11010502036488号