并查集:帮你快速找到"朋友圈老大"的神奇数据结构!
文章目录
今天要给大家介绍一个超实用的数据结构——并查集(Union-Find)!它就像一个高效的"社交关系管理员",能帮你快速找到谁是谁的"朋友圈老大",还能把不同圈子合并成一个大圈子!
1.🧩 并查集能做什么?
想象一下这样的场景:
-
微信里判断两个人是不是同一个好友圈👥
-
游戏中判断两个像素是否相连🎮
-
社交网络中找出所有的兴趣小组🔍
这些都可以用我们的UnionFindSet轻松解决!
2.🔧 我们的"社交关系管理工具包"
template<class T>
class UnionFindSet {
private:
vector<int> _set; // 我们的"社交关系登记簿"
};
这个小小的vector就是我们的核心武器啦!它记录了每个人的"直系上司"信息。
3.📝 工具包三大法宝
3.1初始化:每个人都是自己的老大
UnionFindSet(int n) : _set(n, -1) {}
-
刚开始时,每个人都是独立个体
-
-1表示:“我就是老大!我的圈子有1个人!”
3.2 找老大(FindRoot):顺藤摸瓜
size_t FindRoot(int x) {
while (_set[x] >= 0) { // 只要不是老大
x = _set[x]; // 就继续往上找
}
return x; // 找到终极老大!
}
就像玩"六度空间"游戏,一直往上找,直到找到那个不指向任何人的终极BOSS!
3.3合并圈子(Union):强强联合
void Union(int x1, int x2) {
int root1 = FindRoot(x1);
int root2 = FindRoot(x2);
if (root1 != root2) { // 如果是不同圈子
_set[root1] += _set[root2]; // 合并圈子人数
_set[root2] = root1; // 认root1为新老大
}
}
就像两个公司合并,小公司会被大公司收购,员工都归大公司老板管啦!
4.🌟 超实用小工具
统计朋友圈数量
size_t SetCount() {
size_t count = 0;
for (size_t i = 0; i < _set.size(); i++) {
if (_set[i] < 0) // 找到所有老大
count++;
}
return count;
}
数一数有多少个独立的老大,就知道有多少个不同的朋友圈啦!
5.实现原理
🧠 核心设计思想
并查集要解决两个灵魂拷问:
-
Find:“你的终极老大是谁?”
-
Union:“你们两个团伙现在合并,谁当新老大?”
我们的代码用最优雅的方式回答了这两个问题!
5.1 底层为什么用vector?
vector<int> _set;
设计理由:
-
随机访问:需要快速访问任意元素的关系
-
紧凑存储:数组比链表更适合这种连续查找的场景
-
简单直观:用索引直接表示元素ID
假如不用数组:
-
用
map:浪费空间,查找稍慢 -
用链表:无法快速跳转到父节点
5.2 为什么根节点用负数?
UnionFindSet(int n) : _set(n, -1) {} // 初始化为-1
精妙之处:
-
一举两得:既标记了根节点(负数),又存储了集合大小(绝对值)
-
-1的魔法:
-
符号位:标识这是根节点
-
数值部分:
abs(-1)=1表示集合初始大小
-
内存布局示例:
索引: 0 1 2 3 4
值: -1 -1 -1 -1 -1 // 初始状态,5个独立集合
5.3 FindRoot为什么用while而不用递归?
while (_set[x] >= 0) { x = _set[x]; }
设计考量:
-
避免栈溢出:对于很深的树,递归可能爆栈
-
更高效:循环通常比递归性能更好
-
清晰可见:直接展示查找路径
5.4 Union操作的巧妙设计
void Union(int x1, int x2) {
int root1 = FindRoot(x1);
int root2 = FindRoot(x2);
if (root1 != root2) {
_set[root1] += _set[root2]; // ①
_set[root2] = root1; // ②
}
}
关键点解析:
-
①
_set[root1] += _set[root2]:- 合并集合大小(两个负数相加,如-2 + -3 = -5)
-
②
_set[root2] = root1:- 让root2认root1为父节点(不是交换!)
6.来玩个游戏吧!(测试函数)
UnionFindSet<int> ufs(5); // 初始化5个独立个体
ufs.Union(0, 1); // 0和1成为朋友
ufs.Union(2, 3); // 2和3成为朋友
ufs.Union(1, 2); // 两个圈子合并!
cout << ufs.FindRoot(3); // 输出0,因为最终老大是0
cout << ufs.SetCount(); // 输出2,因为还有独立的4
7.完整代码
template<class T>
class UnionFindSet
{
public:
UnionFindSet(int n)
:_set(n,-1)
{}
size_t FindRoot(int x)
{
while (_set[x] >= 0)
{
x = _set[x];
}
return x;
}
void Union(int x1, int x2)
{
int root1 = FindRoot(x1);
int root2 = FindRoot(x2);
if (root1 != root2)
{
_set[root1] += _set[root2];
_set[root2] = root1;
}
}
size_t SetCount()
{
size_t count = 0;
for (size_t i = 0; i < _set.size(); i++)
{
if (_set[i] < 0)
count++;
}
return count;
}
private:
vector<int> _set;
};
8.其他
🚀 实际应用场景
-
社交网络:快速判断两个人是否有共同好友
-
游戏开发:判断地图上的两个区域是否连通
-
编译器设计:等价变量的合并
-
图像处理:连通区域分析
💡 趣味小挑战
-
试试用并查集解决"岛屿数量"问题!
-
实现一个朋友圈推荐系统,基于共同好友推荐
-
用并查集优化迷宫生成算法
看完了是不是觉得并查集就像社交网络中的"关系大师"?🤵 它用简单的操作就能管理复杂的群体关系!
下次当你需要处理"谁和谁是一伙的"这类问题时,别忘了这个神奇的工具哦!快去试试代码吧~遇到问题欢迎在评论区召唤我! ✨
记得点赞收藏⭐

被折叠的 条评论
为什么被折叠?



