#include <iostream>
#include <vector>
using namespace std;
// 并查集的查找操作
int find(vector<int>& parent, int x) {
if (parent[x] != x) {
parent[x] = find(parent, parent[x]); // 路径压缩
}
return parent[x];
}
// 并查集的合并操作
void unionSets(vector<int>& parent, vector<int>& rank, int x, int y) {
int rootX = find(parent, x);
int rootY = find(parent, y);
if (rootX != rootY) {
// 按秩合并
if (rank[rootX] > rank[rootY]) {
parent[rootY] = rootX;
} else if (rank[rootX] < rank[rootY]) {
parent[rootX] = rootY;
} else {
parent[rootY] = rootX;
rank[rootX] += 1;
}
}
}
int main() {
int N, M;
cin >> N >> M;
vector<int> parent(N + 1);
vector<int> rank(N + 1, 0);
// 初始化并查集
for (int i = 1; i <= N; ++i) {
parent[i] = i;
}
for (int i = 0; i < M; ++i) {
int Mi;
cin >> Mi;
int firstStudent;
cin >> firstStudent;
for (int j = 1; j < Mi; ++j) {
int student;
cin >> student;
unionSets(parent, rank, firstStudent, student);
}
}
// 统计每个代表所代表的学生数量
vector<int> count(N + 1, 0);
for (int i = 1; i <= N; ++i) {
int root = find(parent, i);
count[root]++;
}
// 找到数量最多的代表
int maxFriends = 0;
for (int i = 1; i <= N; ++i) {
if (count[i] > maxFriends) {
maxFriends = count[i];
}
}
cout << maxFriends << endl;
return 0;
}
在书写这个代码的过程当中,我们可能会遇得到的问题总结,可以帮助我们更好的理解代码的书写逻辑。
1)忘记初始化parent和rank数组,导致合并或查找的时候出现问题,导致程序报错。
请加附页。注意学生的编号是从1开始的,但是数组未留出parent[0]的位置。我们通过正确的初始化代码解决该问题,对于数组的大小设置未N+1,索引0不用
(2)出现合并逻辑错误,合并时未正确更新rank,导致树退化成链表,查找效率低。我们通过按秩合并这个方法进行元素之间的合并,可以解决这个问题。
(3)路径压缩未生效,当我们在查找的时候,没有及时更新父节点,导致后续的查找仍然很缓慢,我通过递归路径的方法进行压缩可以避免这个问题的出现。
(4)统计朋友圈大小错误,在直接遍历parent数组统计当中,没有调用find,导致未压缩的父节点影响结果,我通过find函数进行获取根节点,来解决该问题
(5)输入的数据有误,当我粗心出现俱乐部内容的正确的输入,漏写数据或者错误合并等问题,采取利用for循环进行逐行读取并合并