並查集在最近的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
}
}
總結,作者水平有限,有哪裏有問題都歡迎評論指出。如果對你有幫助歡迎點贊。