📅  最后修改于: 2023-12-03 15:21:31.935000             🧑  作者: Mango
不相交集数据结构(Disjoint-set data structure)也被称为并查集(Union-Find)数据结构,是一种用于维护集合的数据结构,支持以下两种操作:
通常的实现方式有两种,一种是使用数组,另一种是使用链表。在本篇文章中,我们将介绍如何使用链表实现不相交集数据结构。
为了实现不相交集数据结构,我们需要用到一个链表节点结构体,其中包含以下成员:
parent
:指向当前节点的父节点。size
:表示该节点所在集合的元素个数(只有集合代表元素节点的 size
才是准确的)。struct Node {
Node* parent;
int size;
};
我们还需要定义一些操作函数,如下所示:
给定一个元素节点 node
,我们要找到其所在集合的代表元素。实现起来很简单,只需要向上遍历 node
的父节点,直到到达某个节点的父节点为 nullptr
,这个节点就是集合的代表元素。
Node* Find(Node* node) {
if (node->parent == nullptr) {
return node;
}
Node* root = Find(node->parent);
node->parent = root; // 路径压缩优化
return root;
}
注意到我们在 Find
过程中顺便做了路径压缩优化,将 node
到根节点路径上的所有节点的 parent
都指向根节点。
给定两个元素节点 node1
和 node2
,我们需要将它们所在的两个集合合并成一个集合。具体地,我们需要找到它们所在集合的代表元素 root1
和 root2
,然后将其中一个代表元素的 parent
指向另一个代表元素。
按照启发式合并(合并时将树大小小的子树的根节点指向树大小大的子树的根节点)的做法,我们可以将集合元素个数少的集合并到元素个数多的集合上,这样可以减少树的高度,提高查询效率。
void Union(Node* node1, Node* node2) {
Node* root1 = Find(node1);
Node* root2 = Find(node2);
// 如果已经在同一个集合中,则直接返回
if (root1 == root2) {
return;
}
// 启发式合并:将元素个数少的集合并到元素个数多的集合上
if (root1->size < root2->size) {
root1->parent = root2;
root2->size += root1->size;
} else {
root2->parent = root1;
root1->size += root2->size;
}
}
给定一个元素节点 node
,我们需要将其作为新集合的代表元素,创建一个新集合。
void MakeSet(Node* node) {
node->parent = nullptr;
node->size = 1;
}
下面的代码演示了如何使用链表实现不相交集数据结构,以及如何使用该数据结构解决一道典型问题:求解连通块个数。
#include <iostream>
#include <vector>
struct Node {
Node* parent;
int size;
};
Node* Find(Node* node) {
if (node->parent == nullptr) {
return node;
}
Node* root = Find(node->parent);
node->parent = root; // 路径压缩优化
return root;
}
void Union(Node* node1, Node* node2) {
Node* root1 = Find(node1);
Node* root2 = Find(node2);
// 如果已经在同一个集合中,则直接返回
if (root1 == root2) {
return;
}
// 启发式合并:将元素个数少的集合并到元素个数多的集合上
if (root1->size < root2->size) {
root1->parent = root2;
root2->size += root1->size;
} else {
root2->parent = root1;
root1->size += root2->size;
}
}
void MakeSet(Node* node) {
node->parent = nullptr;
node->size = 1;
}
int CountConnectedComponents(const std::vector<Node*>& nodes) {
// 初始化不相交集
for (auto node : nodes) {
MakeSet(node);
}
// 对所有边进行 Union 操作
for (int i = 0; i < nodes.size(); ++i) {
Node* node1 = nodes[i];
for (int j = i + 1; j < nodes.size(); ++j) {
Node* node2 = nodes[j];
if (/* Node i and Node j are connected */) {
Union(node1, node2);
}
}
}
// 统计连通块个数
int count = 0;
for (Node* node : nodes) {
if (node->parent == nullptr) {
count++;
}
}
return count;
}
int main() {
// 构建一个无向图,共 5 个节点和 4 条边
std::vector<Node*> nodes(5);
for (int i = 0; i < nodes.size(); ++i) {
nodes[i] = new Node;
}
Union(nodes[0], nodes[1]);
Union(nodes[1], nodes[2]);
Union(nodes[3], nodes[4]);
std::cout << CountConnectedComponents(nodes) << std::endl; // 输出 2
return 0;
}
本篇文章介绍了使用链表实现不相交集数据结构的算法,以及如何使用该数据结构解决一道典型问题:求解连通块个数。这种实现方式虽然效率不如数组实现,但是可以方便地支持动态添加节点。