题目
从一个无向图中提取一棵最小生成树
算法
Kruskal算法首先把图中的边按权值排序(从小到大),依次判断各个边,选取不会在原来的树中构成回路的边 加入到生成树中。
如何判断回路,应用不相交集。待选取的边的两个端点是否同根,如果不同根则可以加入到生成树中,加入完成后,将两个节点Union为同根节点。
算法终止条件:对于n个节点的无向图,完成选取了n-1条边,构成最终的最小生成树。
代码
#include<iostream>
#include<vector>
#include <algorithm>
using namespace std;
typedef pair<pair<int, int>, int> edge_t;
// 这个不相交集的实现,是参考《算法设计与分析》书籍中的伪代码
// 个人觉得不错,可以参考
struct DisjointSet{
vector<int> Set, Rank;
int length;
explicit DisjointSet(int len) {
length = len;
Set = vector<int>(length, -1); // -1表示以自己为根
Rank = vector<int>(length, 0); // 树的高度
}
int Find(int x) {
/*
* 递归法:
if (Set[X] <= 0)
return X;
else
//return Find(Set[X], S);
return Set[X] = Find(Set[X]); // 路径压缩,牺牲树的广度来减小深度
*/
if (Set[x] == -1) {
return x;
}
int y = x;
while (Set[y] != -1) {
y = Set[y];
}
int root = y;
y = x;
while (Set[y] != -1) {
int temp = Set[y];
Set[y] = root;
y = temp;
}
return root;
}
void UnionSet(int x, int y) {
int u = Find(x);
int v = Find(y);
if (Rank[u] <= Rank[v]) {
Set[u] = v;
if (Rank[u] == Rank[v]) {
Rank[v]++;
}
} else {
Set[v] = u;
}
}
};
int main() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<vector<int>> matrix(n, vector<int>(n, 0));
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
cin >> matrix[i][j];
}
}
vector<edge_t> edges;
for (int i = 0; i < n - 1; ++i) {
for (int j = i+1; j < n; ++j) {
edges.emplace_back(make_pair(i, j), matrix[i][j]);
}
}
std::sort(edges.begin(), edges.end(), [](const edge_t& a, const edge_t& b) {
return a.second < b.second;
});
vector<edge_t> res;
DisjointSet set(n);
int index = 0;
while (res.size() < n - 1) {
edge_t tempEdge = edges[index];
int vertex1 = tempEdge.first.first;
int vertex2 = tempEdge.first.second;
if (set.Find(vertex1) != set.Find(vertex2)) {
res.push_back(tempEdge);
set.UnionSet(vertex1, vertex2);
}
index++;
}
int sum = 0;
for (const auto& e : res) {
sum += e.second;
}
// for(auto e : res){
// cout << e.first.first << "<-->" << e.first.second << " weight:" << e.second << "\n";
// }
cout << sum << endl;
}
return 0;
}