并查集

PAT (Advanced Level) Practice 并查集 相关题

  • 《算法笔记》 重点摘要
  • 1034 Head of a Gang (30)
  • 1107 Social Clusters (30)
  • 1118 Birds in Forest (25)

《算法笔记》 9.6 并查集 重点摘要

1. 定义

  • father[i] 表示元素 i的父结点
  • father[i] = i 表示元素 i 为该集合根结点
  • 每个集合只存在一个根结点,且其作为所属集合的标识
int father[N];

2. 初始化

最初每个元素都是一个独立的集合

for (int i = 0; i <= n; i++)
father[i] = i;

3. 查找

对给定结点寻找其根结点:反复寻找父结点直到根结点

  • 递推实现
int findFather (int x){
while (x != father[x])
x = father[x];
return x;
}
  • 递归实现
int findFather (int x){
if (x == father[x]) return x;
else return findFather(father[x]);
}

4. 合并

  • 判断两元素是否在同一集合

    • 分别查找两根结点
    • 判断根结点是否相同
  • 合并集合:其中一个根结点的父节点指向另一个根结点
  • 性质:只对不同集合进行合并,保证同一个集合中不会产生环,即并查集每个集合都是一棵树
void Union (int a, int b){
int faA = findFather(a);
int faB = findFather(b);
if (faA != faB) father[faA] = faB;
}

5. 路径压缩

将当前结点查询路径上所有结点的父节点都指向根节点

  • 递推实现
int findFather (int x){
int y = x; // 将初始查询结点记录下来
while (x != father[x]) // 用 x 进行回溯
x = father[x]; // 找到 x 对应根结点记录在 x 里
while (y != father[y]){ // 用记录的初始查询结点重新回溯
int z = y; //将此结点记录下来
y = father[y]; //向父结点回溯
father[z] = x; //将当前结点指向根结点
}
return x;
}
  • 递归实现
int findFather (int x){
if (x == father[x]) return x;
else{
int F = findFather(father[x]);
father[x] = F;
return F;
}
}

1034 Head of a Gang (30)

题目思路

  • 未作哈希函数,直接用 map<string,string> 存储父子关系,用 map<string,int> 存储结点对应参数
  • 输入时将通话时长记录在个人对应的权重 map 中
  • 若此人未出现过 (father.find(name1) == father.end()),将独立成一个新集合,将其加入 map 并进行初始化 father[name1] = name1
  • 若已经出现过,不必初始化,直接与另一个通话对象 Union 起来
  • 在 Union 函数中,首先查找两个人对应的头目
  • 每加入一个新人就会调用 Union 函数,也就会查找其头目
  • 查找头目时,要保证头目是这个集合中权重最高的
  • 那么每次找到其头目后,要比较头目与新人的权值,若新人权值大,应设置为新的头目
  • 这样每次 Union 得到的都是两个人对应团伙中权值最大的头目,将权值更大的作为新的头目
  • 边输入边处理于是输入完成后可找到每个人对应的头目,遍历一次将团伙总通话时长和总人数分别与头目对应存储起来
  • 最后按标准筛选出符合要求的团伙,放到 map 中
  • 先输出符合要求的团伙数量即对应 map.size() 再按要求输出团伙头目和团伙人数
#include<iostream>
#include<map>
using namespace std;
map<string,string> father;
map<string,int> weight, totaltime, membernum, head_num;
string findFather(string s){
string t = s;
while (father[s] != s) s = father[s];
if (weight[s] < weight[t]){
father[s] = t;
father[t] = t;
return t;
}
return s;
}
void Union(string s1, string s2){
string fa1 = findFather(s1);
string fa2 = findFather(s2);
if (weight[fa1] > weight[fa2]) father[fa2] = fa1;
else father[fa1] = fa2;
}
int main()
{
int n, k, time;
scanf("%d%d", &n, &k);
string name1, name2;
for (int i = 0; i < n; i++){
cin >> name1 >> name2 >> time;
weight[name1] += time;
weight[name2] += time;
if (father.find(name1) == father.end()) father[name1] = name1;
if (father.find(name2) == father.end()) father[name2] = name2;
Union(name1, name2);
}
for (auto it: father){
totaltime[findFather(it.first)] += weight[it.first];
membernum[findFather(it.first)]++;
}
for (auto it: membernum)
if (it.second > 2 && totaltime[it.first] / 2 > k) head_num[it.first] = it.second;
cout << head_num.size() << endl;
for (auto it: head_num) cout << it.first << " " << it.second << endl;
return 0;
}

简化前 findFather 函数

string findFather(string s){
while (father[s] != s){
string fa = father[s];
if (weight[fa] < weight[s]){
if (father[fa] == fa) father[s] = s;
else father[s] = father[fa];
father[fa] = s;
}
else s = father[s];
}
return s;
}

1107 Social Clusters (30)

题目思路

  • 用 set 数组保存每个人的兴趣爱好
  • 输入完成后遍历 set 数组,将每个与之前的人的爱好 set 有重合的进行合并
  • 合并处理完成后,遍历 father 数组,记录根结点即集合数,给每个结点对应根结点记录的集合人数 ++
  • 将 map 按值排序,成对放入 vector,写 cmp 函数实现,最后按要求输出
  • 注意:合并时,要对此人之前所有人的兴趣集合进行检查,不能检查到有相同的就停下,有三个点过不去。

    问题在于如果前面有一个人兴趣为{3},一个人兴趣为{5},现在这个人兴趣为{3,5},在处理这个人之前,前两个人并不会被放入同一个 cluster 中,只有将现在这个人与前两个人分别合并,他们三人才能进入同一个 cluster

#include<iostream>
#include<set>
#include<algorithm>
#include<vector>
#include<unordered_map>
using namespace std;
int father[1001];
set<int> hobbies[1001];
bool cmp (pair<int,int> a, pair<int,int> b){ return a.second > b.second; }
int findFather(int x){
while (x != father[x]) x = father[x];
return x;
}
void Union(int a, int b){
int faA = findFather(a);
int faB = findFather(b);
if (faA != faB) father[faA] = faB;
}
int main()
{
for (int i = 0; i < 1001; i++) father[i] = i;
int n, k, hobby, clusternum = 0;
scanf("%d", &n);
for (int i = 0; i < n; i++){
scanf("%d: ", &k);
for (int j = 0; j < k; j++){
scanf("%d", &hobby);
hobbies[i].insert(hobby);
}
}
for (int i = 1; i < n; i++)
for (int j = 0; j < i; j++)
for (auto it: hobbies[i])
if (hobbies[j].find(it) != hobbies[j].end()) Union(i,j);
unordered_map<int,int> peoplenum;
for (int i = 0; i < n; i++){
if (father[i] == i) clusternum++;
peoplenum[findFather(i)]++;
}
vector<pair<int,int>> sortnum;
for (auto it: peoplenum) sortnum.push_back({it.first,it.second});
sort(sortnum.begin(),sortnum.end(),cmp);
printf("%d\n%d",clusternum,sortnum[0].second);
for (int i = 1; i < sortnum.size(); i++) printf(" %d",sortnum[i].second);
return 0;
}

1118 Birds in Forest (25)

题目思路

  • 鸟编号从 1 开始,father 数组大小要开到 10001,并查集初始化 father[i] = i
  • 每输入一幅画中鸟的数量,先接收输入第一只鸟的编号,再循环接收其他的鸟,将后输入的鸟与第一只鸟合并
  • 用 set 记录鸟的数量,每输入一只鸟就压入 set,set 会自动去重,输入完成后 set 的大小即为鸟的数量
  • 由于鸟编号连续,由 set 大小即可知 father 数组记录到了哪里
  • 遍历记录了鸟信息的部分 father 数组,找到 father[i+1] == i+1 的即为根节点,根结点树即为集合数也即为树的棵数
  • 最后看两只鸟是否在同一棵树,分别查询两只鸟的根结点,比较是否相等即可
#include<iostream>
#include<set>
using namespace std;
int father[10001];
int findFather(int x){
while (x != father[x]) x = father[x];
return x;
}
void Union(int a, int b){
int faA = findFather(a);
int faB = findFather(b);
if (faA != faB) father[faA] = faB;
}
int main()
{
for (int i = 0; i < 10001; i++) father[i] = i;
int n, k, q, q1, q2, fbird, bird, treenum = 0;
scanf("%d", &n);
set<int> birds;
for (int i = 0; i < n; i++){
scanf("%d%d", &k, &fbird);
birds.insert(fbird);
for (int j = 0; j < k - 1; j++){
scanf("%d", &bird);
birds.insert(bird);
Union(fbird,bird);
}
}
for (int i = 0; i < birds.size(); i++)
if (father[i+1] == i+1) treenum++;
printf("%d %d\n", treenum, birds.size());
scanf("%d", &q);
for (int i = 0; i < q; i++){
scanf("%d%d", &q1, &q2);
printf("%s\n", findFather(q1) == findFather(q2) ? "Yes" : "No");
}
return 0;
}

最新文章

  1. 分享一些学习资料-大量PDF电子书
  2. 对于多个数据库表对应一个Model问题的思考
  3. [软件推荐]快速文件复制工具(Limit Copy) V4.0 绿色版
  4. 一次完整的HTTP请求流程
  5. linux网络学习
  6. 博客CSS
  7. 关于libsvm工具箱在64位matlab下的安装说明
  8. Matrix Admin 后台模板笔记
  9. 介绍开源的.net通信框架NetworkComms框架 源码分析(八)SharpZipLibGzipCompressor
  10. java assert
  11. C# 模拟鼠标写字
  12. canvas 绘点图
  13. hadoop学习记录(零)
  14. Python的基本语法,涵盖数据类型、循环判断、列表、map和set等
  15. stray &#39;/241&#39; in program 错误
  16. UI 设计模式 手势识别器
  17. ecshop商城_
  18. 初体验GCP,【福利300$试用金】
  19. 深入理解Java String类(综合)
  20. toString() 数组转字符串

热门文章

  1. Vuex学习心得
  2. uni-app和php交互DES加密解密数据
  3. web目录
  4. 使用spring profile实现多环境切换
  5. Go 语言入门(三)并发
  6. windows下搭建基于nginx的rtmp服务器
  7. 创建Bitmap之Bitmap静态方法使用示例
  8. Animator动画XML实现
  9. webpack——Modules &amp;&amp; Hot Module Replacement
  10. MERN——MongoDB &amp;&amp; React &amp;&amp; Node &amp;&amp; Express