算法学习 并查集(笔试题目:找同伙)
题目背景太长,记得不清楚,暂参考《啊哈算法》一书,根据笔试题目大意改编如下:
警察正要捉获某地区的犯罪团伙,由于强盗人数过大,想查清楚有几个团伙非常困难。
根据上级指示,需要首先尽快抓获强盗A所在的团伙,这需要掌握 1 所在团伙的人数。先有资料如下:
强盗1 和 强盗2 是同伙
强盗3 和 强盗4 是同伙
强盗2 和 强盗5 是同伙
强盗3 和 强盗2 是同伙
注意,强盗的同伙的同伙也是同伙,问 强盗1 的同伙(不包括1自己)有多少人?
该题形式化表示如下:
每个测试实例首先包括2个整数:N(1 <= N <= 1000),M(0 <= M <= N*(N-1)/2),代表现有N个人(用1~N编号)和M组关系;
在接下来的M行里,每行包括2个整数,a,b,代表a跟b是同伙;
当N = 0,M = 0输入结束;
测试样例:
5 4
1 2
2 5
3 2
1 2
3 1
3 2
0 0
输出:
4
0
参考代码如下:
# include <iostream> # include <vector> using namespace std; // 将所有人的master归属为自己 void init_person_table(int person_table[], int nums_person) { for (int i = 0; i < nums_person+1; i++) person_table[i] = i; } // 使用递归方式沿着已知线索 不断往上查找目标人员的leader, // 直到找到真正的BOSS为止 即找到“最高领导人” int find_BOSS(int person_table[], int target) { // 首领为自己(树的根节点) if (person_table[target] == target) return target; else { // 进行路径压缩,每次函数返回的时候顺带将 // “遇到的人的leader”改为大BOSS的编号 // 可以提高下一次找 BOSS 时的查找速度!!! person_table[target] = find_BOSS(person_table, person_table[target]); return person_table[target]; } } // 在人员表中,连结两人的关系, 左边为leader,右边为下属 void merge_relation(int person_table[],int leader, int sub) { // 分别找两人的所属BOSS int a = find_BOSS(person_table, leader); int b = find_BOSS(person_table, sub); if (a != b) { // 按照最小原则,把序号较大者所在的集合并入序号较大者的集合 // 较小者成为较大者的boss person_table[b] = a; // 经过路径压缩后,table[sub]的根的值赋值为leader的祖先table[leader] } } int main() { // 人数与的关系数 int nums_person = 0, nums_relations = 0; vector <int> ans; // 用于统一打印答案 while (1) { nums_person = 0, nums_relations = 0; cin >> nums_person >> nums_relations; // 输入人数与关系数,输入0,0结束 if (nums_person <= 1 || nums_relations == 0) { // 可能存在异常输入 if (nums_person == 0 && nums_relations == 0)// 正常结束 break; else // 默认返回0, 并重新输入 ans.push_back(0); //cout << 0 << endl; continue; } // 根据人数初始化人员列表 int *person_table = new int[nums_person + 1]; init_person_table(person_table, nums_person); for (int i = 0; i < nums_relations; i++) { // 输入两个关系者,默认较小者为per1 (Leader) // 方便统计时找到 “1” 的同伴人数 int per1 = 0, per2 = 0; cin >> per1 >> per2; if (per2 < per1) { int t = per1; per1 = per2; per2 = t; } // 合并两个人的关系集合,较小者作为合并主体 merge_relation(person_table, per1, per2); } // 统计 1 的同伴个数(从2开始找) int count = 0; for (int i = 2; i <= nums_person; i++) { // 集中阶段,因为会存在某些个体的讯号只是他的leader而不是BOSS // 由于 1 的数字最小,不需要归并 // 其实只要满足(person_table[i] != i && person_table[i] != 1)就可以了 if (person_table[i] != i) person_table[i] = find_BOSS(person_table, i); if (person_table[i] == 1) count ++; } /* cout << count << endl; for (int i = 1; i <= nums_person;i++) cout << person_table[i] <<" "; cout << endl; */ ans.push_back(count); // 释放当前内存,等待下批数据 delete[] person_table; } int ans_size = ans.size(); for (int i = 0; i < ans_size; i++) cout << ans[i] << endl; return 0; }