Kruskal算法简介:
(1)将所有边按权值从小到大排序; (2)选出权值最小的边,如果这条边的两个端点不属于同一个集合,就将这条边添加到最小生成树里面; (3)重复(2)直到选出了
节
点
数
?
1
节点数-1
节点数?1 条边;
如果最后选出的边数小于
节
点
数
?
1
节点数-1
节点数?1 ,说明该无向图不能构建出最小生成树。
查并集操作简介: 这里我们假设集合是用树来表示的,也就是每个节点都有一个父节点(根节点的父节点是自身)。 因此通过不断往上一个集合里面的点的父节点,最后都可以找到根节点,也就意味着每个集合是可以用父节点来唯一表示的。
查操作: 查询某个节点属于哪个集合,基于上述的假设,该问题可以转化为查询该节点的根节点。
并操作: 将两个集合合并,自然而然的做法就是将其中一个集合的根节点设置成另一个集合的根节点的子节点。注意,通常情况下我们会将小集合的根节点设置成大节点的子节点,因为小集合的树的深度一般较浅,添加到大集合之后对查询操作比较友好。
查询优化: 基于上述的查操作,我们当然希望每个子节点的父节点就是根节点,这样一次就能得到查询结果。因此,在进行查操作之后,我们可以将查询节点的父节点、查询节点的父节点的父节点等一系列点的父节点设置成根节点。
下面给出一份具有详细注释的源代码,可以很好地帮助大家理解上述算法。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int MAX_N = 500;
int father[MAX_N + 1];
struct Edge {
int u;
int v;
int w;
};
int compareEdgesByW(const Edge& a, const Edge& b) {
return a.w < b.w;
}
bool operator == (const Edge&a, const Edge& b) {
return (a.u == b.u) && (a.v == b.v);
}
int find(int x) {
int nowFather = x;
while(father[nowFather] != nowFather) {
nowFather = father[nowFather];
}
int i = x, j;
while(father[i] != nowFather) {
j = father[i];
father[i] = nowFather;
i = j;
}
return nowFather;
}
void unionSets(int u, int v) {
int uFather = find(u);
int vFather = find(v);
if(uFather != vFather) {
father[uFather] = vFather;
}
}
int main(int argc, const char * argv[]) {
int T;
cin >> T;
while((T--) > 0) {
int n, E;
cin >> n >> E;
vector<Edge> edges;
for(int ei = 0; ei < E; ++ei) {
Edge edge;
cin >> edge.u >> edge.v >> edge.w;
edges.push_back(edge);
}
sort(edges.begin(), edges.end(), compareEdgesByW);
edges.erase(unique(edges.begin(), edges.end()), edges.end());
E = (int)edges.size();
for(int vi = 1; vi <= n; ++vi) {
father[vi] = vi;
}
int result = 0;
int k = 0;
for(Edge edge: edges) {
if(k >= n - 1) {
break;
}
if(find(edge.u) != find(edge.v)) {
unionSets(edge.u, edge.v);
result += edge.w;
++k;
}
}
if(k < n - 1) {
result = -1;
}
cout << result << endl;
}
return 0;
}
|