一、并查集(Union-Find/Disjoint-Set)
并查集是一种树形的数据结构,用来表示不相交集合的数据
作用:检查图上是否存在环。在图上选一条边,如果这条边的两个顶点在同一集合中,则图上存在环
二、优化方法 —— 路径压缩
创建长度为n的数组fathers存储n个结点的父结点。有了这个数组fathers,如果想知道结点i所在的子集的根结点,就可以从结点i开始沿着指向父结点的指针搜索,时间复杂度看起来是O(n),但可以将从结点i到根结点的路径压缩,从而优化时间效率
我们真正关心的是结点i的根结点是谁而不是它的父结点,因此可以在fathers[i]中存储它的根结点。当第1次找结点i的根结点时,还需要沿着指向父结点的边遍历直到找到根结点。一旦找到了它的根结点,就把根结点存放到fathers[i]中。不仅如此,还可以一起更新从结点i到根结点的路径上所有结点的根结点。以后只需要O(1)的时间就能知道这些结点的根结点。这种优化叫作路径压缩,因为从结点i到根结点的路径被压缩成若干长度为1的路径
三、优化方法 —— 按秩合并
按秩合并分为按大小合并和按深度合并两种
四、时间复杂度
五、代码实现(路径压缩+按深度合并)
#include<iostream>
using namespace std;
const int VERTICES = 6;
void initialise(int parent[], int rank[]) {
for (int i = 0; i < VERTICES; i++) {
parent[i] = -1;
rank[i] = 0;
}
}
int find_root(int x, int parent[]) {
int x_root = x;
while (parent[x_root] != -1) {
x_root = parent[x_root];
}
return x_root;
}
int union_vertices(int x, int y, int parent[], int rank[]) {
int x_root = find_root(x, parent);
int y_root = find_root(y, parent);
if (x_root == y_root) {
return 0;
} else {
if (rank[x_root] > rank[y_root]) {
parent[y_root] = x_root;
} else if (rank[x_root] < rank[y_root]) {
parent[x_root] = y_root;
} else {
parent[x_root] = y_root;
++rank[y_root];
}
return 1;
}
}
int main() {
int parent[VERTICES];
int rank[VERTICES];
int edges[6][2] = {
{0, 1}, {1, 2}, {1, 3},
{3, 4}, {2, 5}, {5, 4}
};
initialise(parent, rank);
for (int i = 0; i < 6; i++) {
int x = edges[i][0];
int y = edges[i][1];
if (union_vertices(x, y, parent, rank) == 0) {
cout << "Cycle detected!";
exit(0);
}
}
cout << "No cycles found.";
return 0;
}
六、代码实现(路径压缩+按大小合并)
class UnionFind {
// 连通分量个数
private int count;
// 存储每个节点的父节点
private int[] parent;
// 记录每棵树的“重量”
private int[] size;
public UnionFind(int n) {
this.count = n;
parent = new int[n];
size = new int[n];
for (int i = 0; i < n; ++i) {
parent[i] = i;
size[i] = 1;
}
}
public void union(int p, int q) {
int rootP = find(p);
int rootQ = find(q);
if (rootP == rootQ) return;
if (size[rootP] > size[rootQ]) {
parent[rootQ] = rootP;
size[rootP] += size[rootQ];
} else {
parent[rootP] = rootQ;
size[rootQ] += size[rootP];
}
--count;
}
public boolean connected(int p, int q){
int rootP = find(p);
int rootQ = find(q);
return rootP == rootQ;
}
private int find(int x) {
while (parent[x] != x) {
// 进行路径压缩
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
public int count() {
return count;
}
}
七、代码实现(路径压缩+按大小合并)
#include<iostream>
using namespace std;
struct UnionFind {
// 连通分量个数
int count;
// 存储每个节点的父节点
int* parent;
// 记录每棵树的“重量”
int* size;
void init(int n) {
count = n;
parent = new int[n];
size = new int[n];
for (int i = 0; i < n; ++i) {
parent[i] = i;
size[i] = 1;
}
}
void _union(int p, int q) {
int rootP = find(p);
int rootQ = find(q);
if (rootP == rootQ) return;
if (size[rootP] > size[rootQ]) {
parent[rootQ] = rootP;
size[rootP] += size[rootQ];
} else {
parent[rootP] = rootQ;
size[rootQ] += size[rootP];
}
--count;
}
bool connected(int p, int q){
int rootP = find(p);
int rootQ = find(q);
return rootP == rootQ;
}
int find(int x) {
while (parent[x] != x) {
// 进行路径压缩
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
int _count(){
return count;
}
};
int main()
{
return 0;
}