概念
三个主要操作
-
没有出现闭环的情况
-
没有出现闭环的情况
-
代码实现
// 一定要结合图看,简单直接
#include <stdio.h>
#include "stdlib.h"
/// 最大定点数量
#define VERTICES 6
/// 初始化
void initialize(int parent[], int rank[]) {
int i;
for (i = 0; i < VERTICES; i++) {
parent[i] = -1;
rank[i] = 0;
}
}
/// find,朴素查找元素的根,非递归
int find_root(int x, int parent[]) {
int x_root = x;
while (parent[x_root] != -1) {
x_root = parent[x_root];
}
return x_root;
}
#if 0
/// 1 - Union successfully, 0 - union failed
/// 缺点:如果union过程是1->2, 2->3, 3->4..... 存在的问题:时间复杂度O(n),会造成很长的链,树高度越小越好,可优化
/// 1 - Union successfully, 0 - union failed
int union_vertices(int x, int y) {
int x_root = find_root(x, parent);
int y_root = find_root(y, parent);
if (x_root == y_root) {
return 0;
} else {
parent[x_root] = y_root;
return 1;
}
}
#endif
#if 1
/// 对上面union_vertices函数的优化:新增rank数组,如果rank[x] < rank[y],则parent[x_root] = y_root; 如果rank[y] < rank[x],则parent[y_root] = x_root; 如果相等,则parent[x_root] = y_root, rank[y]++;
int union_vertices(int x, int y, int parent[], 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]++;
}
// parent[x_root] = y_root;
return 1;
}
}
#endif
int main(int argc, const char * argv[]) {
printf("Hello, 并查集!\n");
int parent[VERTICES] = {0};
int rank[VERTICES] = {0};
int edges[6][2] = {
{0, 1}, {1, 2}, {1, 3}, {3, 4}, {2, 4}, {2, 5}
};
initialize(parent, rank);
int i;
for (i = 0; i < sizeof(edges)/sizeof(edges[0]); i++) {
int x = edges[i][0];
int y = edges[i][1];
if (union_vertices(x, y, parent, rank) == 0) {
printf("Circle detected!\n");
exit(1);
}
}
printf("No cycles found!\n");
return 0;
}
补充: 路径压缩实现
使用并查集查找时,如果查找次数很多,那么使用朴素版的查找方式肯定要超时。比如,有一百万个元素,每次都从第一百万个开始找,这样一次运算就是106,如果程序要求查找个一千万次,这样下来就是1013,肯定要出问题的。
这是朴素查找的代码,适合数据量不大的情况:
void find(int x) {
int r = x;
while (parent[r] != r) {
r = parent[r];
}
return r;
}
下面是采用路径压缩的方法查找元素:
//查找x元素所在的集合,回溯时压缩路径
int find(int x) {
if (x != parent[x]) {
//从x结点搜索到祖先结点所经过的结点都指向该祖先结点
parent[x] = find(parent[x]);
}
return parent[x];
}
上面是一采用递归的方式压缩路径, 但是,递归压缩路径可能会造成溢出栈,下面我们说一下非递归方式进行的路径压缩:
int find(int x) {
int t, k, r;
r = x;
//查找跟节点
while (parent[r] != r) {
r = parent[r];
}
//非递归路径压缩操作
k = x;
while (k != r) {
t = parent[k];
parent[k] = r;
k = t;
}
return r;
}