【PAT算法之路】 -- 並查集 1021 Deepest Root (25 分) C++解法

【PAT算法之路】 – 專欄總攬

並查集在最近的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
        }
    }

總結,作者水平有限,有哪裏有問題都歡迎評論指出。如果對你有幫助歡迎點贊。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章