由完全图构建最小生成树,主要有两种方法:1. Kruskal算法;2. Prim算法;
Kruskal算法
需要构建并查集检查每次新添加的边构不构成回路。
// 本质是求解一个完全图的最小生成树;
// 使用kruskal算法需要通过并查集判断连通性,使用Prim算法则不用;
// 首先需要将所有边都提取到一个边集里面,然后对边长进行排序;
// 每次选取最短的边,然后通过并查集判断连通性
// lion is hungry
class DisjointSet {
private:
vector<int> f, rank; // 按size大小进行优化
public:
DisjointSet(int n): f(n), rank(n) {
for (int i = 0; i < n; ++i) {
f[i] = i;
rank[i] = 1;
}
}
int findf(int x) {
if (f[x] != x) {
f[x] = findf(f[x]);
}
return f[x];
}
bool unionSet(int x, int y) { // 判断是否处于同一个连通分量并合并
int fx = findf(x), fy = findf(y);
if (fx == fy) return false;
if (rank[fx] < rank[fy]) swap(fx, fy);
rank[fx] += rank[fy];
f[fy] = fx;
return true;
}
};
struct Edge() { // 使用一个struct表示一条边,要存储边的长度以及两个顶点
int len, x, y;
Edge(int len, int x, int y) : len(len), x(x), y(y) {};
}
class Solution {
public:
int minCostConnectPoints(vector<vector<int>>& points) {
auto dist = [&] (int x, int y) -> int { // 声明一个匿名函数,用来计算两点之间的曼哈顿距离
return abs(points[x][0] - points[y][0]) + abs(points[x][1] - points[y][1]);
}
int n = points.size();
DisjointSet dsu(n);
vector<Edge> edges;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
edges.emplace_back({dist(i, j), i, j});
}
}
sort(edges.begin(), edges.end(), [](Edge a, Edge b) -> int {return a.len < b.len}); // 按升序的顺序排列所有边
int ret = 0, num = 1;
for (auto& [len, x, y] : edges) {
if (dsu.unionSet(x, y)) {
ret += len;
num++;
if (num == n) {
break;
}
}
}
return ret;
}
};
Prim算法
每次寻找距离当前已生成的连通分量距离最近的顶点加入。
class Solution {
public:
int prim(vector<vector<int> >& points, int start) {
int n = points.size();
int res = 0;
// 将points转换成临接矩阵
vector<vector<int>> g(n, vector<int>(n));
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
int dist = abs(points[i][0] - points[j][0]) + abs(points[i][1] - points[j][1]);
g[i][j] = dist;
g[j][i] = dist;
}
}
vector<int> lowcost(n, INT_MAX); // 记录Vi到已生成连通分量的最短距离
vector<int> v(n, -1); // 记录Vi是否已经加入了所生成的连通分量
// 先将start结点加入生成树,然后更新距离
v[start] = 0;
for (int i = 0; i < n; ++i) {
if (i == start) continue;
lowcost[i] = g[i][start];
}
// 加入剩下n - 1个结点
for (int i = 1; i < n; ++i) {
// 要执行n - 1次以添加n - 1个结点
int minIdx = -1;
int minVal = INT_MAX;
// 选出距离当前连通分量距离最近的顶点
for (int j = 0; j < n; ++j) {
if (v[j] == 0) continue;
if (lowcost[j] < minVal) {
minIdx = j;
minVal = lowcost[j];
}
}
// 加入这个顶点并更新距离
res += minVal;
v[minIdx] = 0;
lowcost[minIdx] = -1;
for (int j = 0; j < n; ++j) {
if (v[j] == -1 && lowcost[j] > g[minIdx][j]) {
lowcost[j] = g[minIdx][j];
}
}
}
return res;
}
int minCostConnectPoints(vector<vector<int>>& points) {
return prim(points, 0);
}
};
采用堆优化的Prim算法。
虽然感觉这个堆优化然并卵。。。
class Solution {
public:
int minCostConnectPoints(vector<vector<int>>& points) {
int n = points.size();
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq; // 优先队列, pair的第一个元素表示距离,第二个元素表示下标
vector<bool> v(n, false); // 用来记录一个结点是否已经加入了连通分量;
int result = 0;
pq.push({0, 0});
while (!pq.empty()) {
auto [dist, i] = pq.top();
pq.pop();
if (v[i]) continue; // 这个点已经加入
result += dist;
v[i] = true;
for (int j = 0; j < n; ++j) {
if (v[j]) continue;
int newDist = abs(points[i][0] - points[j][0]) + abs(points[i][1] - points[j][1]);
pq.push({newDist, j});
}
}
return result;
}
};