- 💂 个人主页:努力学习的少年
- 🤟 版权: 本文由【努力学习的少年】原创、在CSDN首发、需要转载请联系博主
- 💬 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦
一.并查集的定义
1. 并查集是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。
2. 并查集通常包含两种操作
- 查找(Find):查询两个元素是否在同一个集合中
- 合并(Union):把两个不相交的集合合并为一个集合
二.并查集的实现
- 如果有n个元素可能要合并,那么我们需要定义一个n个大小的元素的数组.
- 数组的下标表示的是元素的编号,例如数组下标为1,则代表元素1.
- 所有元素都初始化为-1,代表每个元素都是各自的集合,因此起初有n个集合
当有些集合 合并之后,数组会出现如下这种情况:
- 如果元素的值是负数,则代表该元素是某个集合的根节点,负数的绝对值是该集合的个数,且一个集合只有一个根节点.
如:元素1的值是-4,则说明元素1是某个集合的根节点,且该集合的元素个数是4.
- 如果元素的值是正数,它仅仅是某个集合中的普通节点,且元素的值是它的父亲元节点的编号。
如:元 素2的值是1,则说明它的父亲节点的编号是元素1.
并查集的查找(Find)
并查集的查找是判断两个元素是否在同一个集合上。
例:怎样判断元素2和元素6元素是否在同一个集合上?
判断元素2和元素6的是否在同一个集合上,我们只需要判断元素2对应的集合的根节点与元素5对应集合的根节点是否相同,因为一个集合只有一个根节点,可见元素2对应的集合的根节点是元素1
元素6的根节点的对应的集合的根节点是元素4,因此它们不在同一个集合上。
判断两个元素是否在同一个集合只需要找到他们相对应的根节点,那么如何找到一个元素对应集合的根节点?
在一个集合当中,只有根节点它的值是负数,其余的元素都是正数,因此,给我们一个元素,我们需要判断对应的值是否为负数,如果不是,在通过元素的值,找到它的父亲节点的元素,再判断父亲节点是否负数,如此循环,直到找到一个元素,它的值为负数,则为该集合的根节点。
以元素6查找集合的根节点为例:
- 元素6它存放的值是5,不是一个负数,说明元素6不是根节点,跳转到元素5.
- 元素5存放的4,不是一个负数,说明元素5不是根节点,跳转到元素4.
- 元素4存放的是-3,是一个负数,说明元素4就是元素6所对应集合的根节点。
Find的实现:
bool Find(const int x, const int y) {
int root_x = x, root_y = y;
while (v[root_x] >= 0) {
root_x = v[root_x];
}
while (v[root_y] >= 0) {
root_y = v[root_y];
}
return root_x == root_y;
}
并查集的合并
并查集的合并是将一个集合合并到另一个集合上,并为一个更大的集合。
如果有两个元素,它们之间有交集,此时我们想要将这两个元素的对应的集合合并为一个集合,那么该如何合并呢?
如下:元素8和元素6它们之间存在交集,此时我们想要将8元素对应的集合与6元素对应的集合合并成一个集合,如何合并?
合并操作:
- 先找到8元素对应集合的根节点,也就是元素1,再去找元素6对应集合的根节点,也就是元素4。
- 集合1的个数加上集合2的个数,生成一个更大的集合,也就是元素1的内容加上元素4的内容。
- 集合2的根节点值存放集合1对应的编号,表示的是集合2合并到集合1上。也就是将元素4的内容存放的元素1的编号。
代码实现:
bool Union(const int x, const int y) {
int root_x = x, root_y = y;
while (v[root_x] >= 0) {
root_x = v[root_x];
}
//此时root_x为负数,则为x相对应集合的根节点
while (v[root_y] >= 0) {
root_y = v[root_y];
}
//此时root_y为负数,则为y相对应集合的根节点
if (root_x == root_y) {
//两个元素是在同一个集合上
//合并失败
return false;
}
v[root_x] += v[root_y];
v[root_y] = root_x;
return true;
}
并查集整体代码
class unionfind1 {
public:
unionfind1(int* x, size_t n) {
for (int i = 0; i < n; i++) {
v.push_back(-1);
}
}
bool Find(const int x, const int y) {
int root_x = x, root_y = y;
while (v[root_x] >= 0) {
root_x = v[root_x];
}
while (v[root_y] >= 0) {
root_y = v[root_y];
}
return root_x == root_y;
}
bool Union(const int x, const int y) {
int root_x = x, root_y = y;
while (v[root_x] >= 0) {
root_x = v[root_x];
}
//此时root_x为负数,则为x相对应集合的根节点
while (v[root_y] >= 0) {
root_y = v[root_y];
}
//此时root_y为负数,则为y相对应集合的根节点
if (root_x == root_y) {
//两个元素是在同一个集合上
//合并失败
return false;
}
v[root_x] += v[root_y];
v[root_y] = root_x;
return true;
}
private:
vector<int> v;
};
如果并查集中的元素不是一个正数,可能是一个字符串,那么上面的并查集是不能帮我们实现的。
所以我们需要加一层哈希表将 字符串 与 数组 下标映射在一起,当判断两个元素是否在同一个集合中或者合并两个元素相对应的集合,需要先通过哈希表将 字符串 转换为 数组 下标。
template<class T>
class unionfind {
public:
unionfind(T* x,size_t n) {
for (int i = 0; i < n; i++) {
v.push_back(-1);
//将T类型与数组下标映射在一起
mp.insert(pair<T,size_t>(x[i], i));
}
}
bool Find(const T x, const T y) {
//通过x,y找到相对应的数组下标
size_t index_x = mp[x];
size_t index_y = mp[y];
while (v[index_x] >= 0) {
index_x = v[index_x];
}
while (v[index_y] >= 0) {
index_y = v[index_y];
}
return index_x == index_y;
}
bool Union(const T x, const T y) {
//通过x,y找到相对应的数组下标
size_t index_x = mp[x];
size_t index_y = mp[y];
while (v[index_x] >= 0) {
index_x = v[index_x];
}
while (v[index_y] >= 0) {
index_y = v[index_y];
}
if (index_x == index_y) {
//两个元素是在同一个集合上
//合并失败
return false;
}
v[index_x] += v[index_y];
v[index_y] = index_x;
return true;
}
private:
vector<int> v;
unordered_map<T, int> mp;
};
相关题目
剑指 Offer II 116. 省份数量 |
684. 冗余连接 |
947. 移除最多的同行或同列石头 |