简介
并查集(Disjoint-Set Union,简称并查集,也叫做Union-Find数据结构)是一种用于管理集合的数据结构,通常用于解决一些与集合分割、连接以及查询集合成员等问题。
高效的合并和查找操作:并查集的核心操作是合并(Union)和查找(Find),这两个操作通常都能在接近常数时间内完成,因此非常高效。这使得并查集在处理一些需要频繁合并和查找集合的问题时非常有优势,例如连通性问题和集合的合并与查找。
建立一个并查集
初始化时 每一个元素都要建立一个小集合 如a b c d e f 建立集合{a},{b},{c},{d},{e},{f}
template<typename V>
class Node {
public:
V value;
Node(V v) : value(v) {}
};
template<typename V>
class UnionSet {
public:
unordered_map<V, Node<V>> nodes;
unordered_map<Node<V>, Node<V>> parents;
unordered_map<Node<V>, int> sizeMap;
UnionSet(vector<V> values) {
for (V cur : values) {
Node<V> node(cur);
nodes[cur] = node;
parents[node] = node;
sizeMap[node] = 1;
}
}
};
UnionSet 模板类: 这是表示并查集的主要模板类。它包含以下成员:
unordered_map<V, Node<V>> nodes
:用于存储节点值到节点对象的映射。这个映射会将节点的值与对应的节点对象关联起来,以便能够在O(1)时间内查找节点。unordered_map<Node<V>, Node<V>> parents
:用于存储节点对象到其父节点对象的映射。这个映射表示了节点之间的父子关系,帮助实现并查集的合并和查找操作。unordered_map<Node<V>, int> sizeMap
:用于存储节点对象到其所在集合的大小的映射。这个映射用于优化合并操作,确保将较小的集合合并到较大的集合中,以减小树的高度,提高性能。- 构造函数
UnionSet(vector<V> values)
:接受一个值的向量作为参数,然后根据这些值初始化并查集的节点和相关数据结构。在构造函数中,对每个值创建一个节点对象,并将其添加到nodes
映射中,同时将其设置为自己的父节点,以及将其所在集合的大小初始化为1。
代表结点:代表节点(Representative Node)是指每个集合中的一个特定节点,用来代表该集合的所有节点。这个代表节点通常被用作集合的标识,
简单来说 就是不能再往下指的结点
常见操作
判断是否在同一集合
findFather用于找祖先
a到f一个一个压入栈 然后依次弹出 父改成f
判断是否在同一个集合只需要看代表结点是否相等
template<typename V>
Node<V> findFather(Node<V> cur) {
stack<Node<V>> path;
while (cur != parents[cur]) {
path.push(cur);
cur = parents[cur];
}
while (!path.empty()) {
parents[path.top()] = cur;
path.pop();
}
return cur;
}
template<typename V>
bool isSameSet(V a, V b) {
Node<V> nodeA = nodes[a];
Node<V> nodeB = nodes[b];
Node<V> fatherA = findFather(nodeA);
Node<V> fatherB = findFather(nodeB);
return fatherA == fatherB;
}
- 从给定节点
cur
开始,通过不断向上追溯父节点,找到其所属集合的代表节点(根节点)。 - 在查找过程中,使用栈
path
记录路径上的节点,以便后续路径压缩。 - 一旦找到代表节点,将路径上的所有节点的父节点都直接设置为代表节点,以降低树的高度。
- 返回找到的代表节点,表示节点所属的集合。
合并两个集合
template<typename V>
void unionSets(V a, V b) {
Node<V> aHead = findFather(nodes[a]);
Node<V> bHead = findFather(nodes[b]);
if (aHead != bHead) {
int aSetSize = sizeMap[aHead];
int bSetSize = sizeMap[bHead];
Node<V> big = (aSetSize >= bSetSize) ? aHead : bHead;
Node<V> small = (big == aHead) ? bHead : aHead;
parents[small] = big;
sizeMap[big] = aSetSize + bSetSize;
sizeMap.erase(small);
}
}
简析:
找到元素 a 和 b 所在集合的代表节点 aHead 和 bHead。
如果它们的代表节点不同,表示它们不在同一个集合中,执行合并操作。
在合并操作中,选择较大的集合作为新的代表节点 big,将较小的集合合并到 big 中。
更新 parents 映射,将较小集合的代表节点的父节点设置为 big,表示合并完成。
更新 sizeMap 映射,将 big 的大小更新为两个集合的大小之和,同时移除 small 的大小信息
优化:用数组实现
哈希表是为了方便理解 ,做题的时候往往用数组实现效率更高
public:
UnionFind(int N) {
parent.resize(N);
size.resize(N);
help.resize(N);
sets = N;
for (int i = 0; i < N; i++) {
parent[i] = i;
size[i] = 1;
}
}
int find(int i) {
int hi = 0;
while (i != parent[i]) {
help[hi++] = i;
i = parent[i];
}
for (hi--; hi >= 0; hi--)
parent[help[hi]] = i;
return i;
}
void union2(int i, int j) {
int f1 = find(i); int f2 = find(j);
if (f1 != f2) {
if (size[f1] >= size[f2]) {
size[f1] += size[f2];
parent[f2] = f1;
}
else {
size[f2] += size[f1];
parent[f1] = f2;
}
sets--;
}
}
};
岛问题
题目摘要:01矩阵 相邻的1的区域是岛 求岛的数量。相邻只包括左右上下不包括斜上下。
递归解法
int numIslands3(vector<vector<char>>& board) {
int islands = 0;
for (int i = 0; i < board.size(); i++) {
for (int j = 0; j < board[0].size(); j++) {
if (board[i][j] == '1') {
islands++;
infect(board, i, j);
}
}
}
return islands;
}
void infect(vector<vector<char>>& board,int i,int j){
if (i < 0 || i == board.size() || j < 0 || board[0].size() || board[i][j]!='1')
return;
board[i][j] = 2;
infect(board, i - 1, j);
infect(board, i + 1, j);
infect(board, i, j+1);
infect(board, i, j-1);
}
并查集解法
方法一:建立dot类 让01矩阵的0 1各自对应一个地址 如果为1就建立dot 每个dot地址不一样
如果为0 则为空
class Solution {
public:
int numIslands(std::vector<std::vector<char>>& board) {
int row = board.size();
int col = board[0].size();
// 创建并查集对象
UnionFind uf(row * col);
for (int j = 1; j < col; j++) {
if (board[0][j - 1] == '1' && board[0][j] == '1') {
uf.union2(j - 1, j);
}
}
for (int i = 1; i < row; i++) {
if (board[i - 1][0] == '1' && board[i][0] == '1') {
uf.union2((i - 1) * col, i * col);
}
}
for (int i = 1; i < row; i++) {
for (int j = 1; j < col; j++) {
if (board[i][j] == '1') {
if (board[i][j - 1] == '1') {
uf.union2(i * col + (j - 1), i * col + j);
}
if (board[i - 1][j] == '1') {
uf.union2((i - 1) * col + j, i * col + j);
}
}
}
}
// 在这里使用并查集对象 `uf` 进行进一步的处理
return uf.sets;
}
};
方法二:构造映射
建立一个一维数组 用对应方法讲二维矩阵一一对应到一维数组
对应方法:(i,j)->i*列+j
UnionFind2(std::vector<std::vector<char>>& board) {
col = board[0].size();
sets = 0;
int row = board.size();
int len = row * col;
parent.resize(len);
size.resize(len);
help.resize(len);
for (int r = 0; r < row; r++) {
for (int c = 0; c < col; c++) {
if (board[r][c] == '1') {
int i = index(r, c);
parent[i] = i;
size[i] = 1;
sets++;
}
}
}
}
int index(int r, int c) {
return r * col + c;
}
int find(int i) {
int hi = 0;
while (i != parent[i]) {
help[hi++] = i;
i = parent[i];
}
for (hi--; hi >= 0; hi--) {
parent[help[hi]] = i;
}
return i;
}
void union2(int r1, int c1, int r2, int c2) {
int i1 = index(r1, c1);
int i2 = index(r2, c2);
int f1 = find(i1);
int f2 = find(i2);
if (f1 != f2) {
if (size[f1] >= size[f2]) {
size[f1] += size[f2];
parent[f2] = f1;
}
else {
size[f2] += size[f1];
parent[f1] = f2;
}
}
sets--;
}