并查集在最近的PAT中也较常出现,并查集本身的代码非常优雅、简洁,如果第一次接触一定会惊叹它的简洁的。本身代码大概20行。在理解的基础上直接背下就行。我们选一个难一点的题来举例吧!
1021 Deepest Root (25 分)
A graph which is connected and acyclic can be considered a tree. The height of the tree depends on the selected root. Now you are supposed to find the root that results in a highest tree. Such a root is called the deepest root.
Input Specification:
Each input file contains one test case. For each case, the first line contains a positive integer N (≤10
4
) which is the number of nodes, and hence the nodes are numbered from 1 to N. Then N−1 lines follow, each describes an edge by given the two adjacent nodes’ numbers.
Output Specification:
For each test case, print each of the deepest roots in a line. If such a root is not unique, print them in increasing order of their numbers. In case that the given graph is not a tree, print Error: K components where K is the number of connected components in the graph.
Sample Input 1:
5
1 2
1 3
1 4
2 5
Sample Output 1:
3
4
5
Sample Input 2:
5
1 3
1 4
2 5
3 4
Sample Output 2:
Error: 2 components
给出n个结点(1~n)之间的n条边,问是否能构成一棵树,如果不能构成则输出它有的连通分量个数,如果能构成一棵树,输出能构成最深的树的高度时,树的根结点。如果有多个,按照从小到大输出。
一开始我也是没有思路的 ╮(╯▽╰)╭,直到看到下面的算法:
先一遍遍历得到深度最大的所有点的集合,再从集合中随便找一个节点,从该点出发再做一遍遍历,得到另一个深度最大的所有点的集合,两个集合的并集即为结果。证明可以看《算法笔记》,我也没有看,看也看不懂╮(╯▽╰)╭
我用的并查集确定是否连通,用bfs遍历,容我先给出代码:
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
using namespace std;
vector<int> g[10010];
bool vis[10010];
int father[10010];
int findFather(int a){
if(a != father[a])
father[a] = findFather(father[a]);
return father[a];
}
void Union(int a,int b){
int fathera = findFather(a);
int fatherb = findFather(b);
if(fathera != fatherb)
father[fathera] = fatherb;
}
set<int> last_vec;
void bfs(int s){
fill(vis,vis+10010,false);
queue<int> q;
q.push(s);
vis[s] = true;
while(!q.empty()){
int len = q.size();
set<int> vec;
for(int i=0;i<len;i++){
int now = q.front();
vec.insert(now);
q.pop();
for(auto item:g[now])
if(!vis[item]){
q.push(item);
vis[item] = true;
}
}
last_vec = vec;
}
}
int main() {
for(int i=0;i<10010;i++){
father[i] = i;
}
int n;
cin >> n;
for(int i=1;i<n;i++){
int a,b;
cin >> a >> b;
g[a].push_back(b);
g[b].push_back(a);
Union(a,b);
}
set<int> root;
for(int i=1;i<=n;i++){
root.insert(findFather(i));
}
if(root.size() > 1){
printf("Error: %d components",root.size());
return 0;
}
/////////////////////////////////////////
bfs(1);
set<int> res = last_vec;
bfs(*(res.begin()));
res.insert(last_vec.begin(),last_vec.end());
for(auto item:res){
cout << item << endl;
}
return 0;
}
因为这一篇主要讲并查集,所以我们看分割线上面的部分,根据并查集判断是否连通。
并查集的主要思想就是同一类的结点,它们的根结点一定相同,这里的根结点就是father[x] == x的结点。而对于一个集合中,谁当根结点并不重要,但是有且只有一个。
一、定义和初始化
首先记住int father[](生子当如孙仲谋 – 我是你xx),然后一定在读入数据前就初始化father,让每个元素当自己的father。即每一个结点初始化都是各管各的,没有交集。
二、定义findFather、Union
狠一点,把函数名都记下吧(选你记得住的),findFather是选择当前元素的父亲。Union是合并两个元素,让它们有共同的父亲。
findFather尤其用递归方式非常简洁,上面的方法还带路径压缩,就是比如:a的父亲b的父亲c的父亲为d,就直接变成了a的父亲为d。father[a] == d,这在你查询父亲时就直接完成了。
Union 就记住一句话:选个人当父亲,即比如:结点集合A的结点a,和结点集合B的结点b需要Union,那么它们各自集合的根节点选一个当根节点。
如果上面的话看晕了,先百度理解下并查集。
三、输入数据时完成设置父亲或者Union
根据题意完成,比如这道题是给的两个点,就直接Union就行,还一些题如1107需要设置一个数组,根据题意Union.
最后再提一下BFS,对于PAT的大部分遍历都可以用BFS,柳婼大神貌似更习惯用DFS,反正我太菜了,复杂的递归看起来头晕。而对于BFS,需要注意的是一定掌握下面这种写法,即一次while必须把上一次存的元素用完,这样有很多好处,比如:对于树的层次遍历可以得到树的层数,对于图的遍历,可以处理每一圈的元素,比如这道题。等等。。
while(!q.empty()){
int len = q.size();
// 根据需要定义一些变量
for(int i=0;i<len;i++){
int now = q.front();
q.pop();
// 根据需要执行一些操作
// 根据情况q.push
}
}
总结,作者水平有限,有哪里有问题都欢迎评论指出。如果对你有帮助欢迎点赞。
来源:CSDN
作者:国度
链接:https://blog.csdn.net/qq_40515692/article/details/103240066