藍橋杯 試題 歷屆試題 發現環 並查集+dfs

問題描述
  小明的實驗室有N臺電腦,編號1~N。本來這N臺電腦之間有N-1條數據連接相連,剛好構成一個樹形網絡。在樹形網絡上,任意兩臺電腦之間有惟一的路徑相連。


  不過在最近一次維護網絡時,管理員誤操做使得某兩臺電腦之間增長了一條數據連接,因而網絡中出現了環路。環路上的電腦因爲兩兩之間再也不是隻有一條路徑,使得這些電腦上的數據傳輸出現了BUG。


  爲了恢復正常傳輸。小明須要找到全部在環路上的電腦,你能幫助他嗎?
輸入格式
  第一行包含一個整數N。
  如下N行每行兩個整數a和b,表示a和b之間有一條數據連接相連。


  對於30%的數據,1 <= N <= 1000
  對於100%的數據, 1 <= N <= 100000, 1 <= a, b <= N


  輸入保證合法。
輸出格式
  按從小到大的順序輸出在環路上的電腦的編號,中間由一個空格分隔。
樣例輸入
5
1 2
3 1
2 4
2 5
5 3
樣例輸出
1 2 3 5

解題思路:這一題能夠用並查集的數據結構判斷是否有環,用dfs遞歸找到環。

並查集:並查集是用來管理元素分組狀況的數據結構。注意並查集並不能高效的進行分割,能夠高效地進行:
  • 查詢a,b是否同組。
  • 合併a,b所在的組。

具體實現用樹形結構,若是a,b所在組相同,則a,b有相同的根結點。合併兩組時即讓一組的根向另外一組相連。網絡

爲了不樹形結構下發生退化的狀況,在並查集中能夠以下操做:數據結構

  • 對於每顆樹,記錄這顆樹的高度.(rank)
  • 合併時,rank小的向rank大的連

此外還能夠路徑壓縮,即把每一個結點都直接與其根結點相連,樹的高度爲2.在查詢過程當中查詢過程當中遞歸的全部節點都直接指向根結點。spa

此時爲方便起見,即便樹的高度改變,咱們也不修改rank的值(rank只是在合併時決定誰向誰連根)。code

具體見下實現代碼。blog


 

若(u,v)之間有邊,則將u,v放入同一組。若是在放入u,v以前已經在同一組,即從u能夠到v,那麼加入邊(u,v)後就造成了一個環。遞歸

以u或v做爲環的起點,用dfs方法遍歷每一個頂點相鄰的頂點,看最後是否回到了起點,若是沿某一路徑回到起點,則說明這些it

頂點在這個環內。io


實現代碼:

 

#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;

const int Max_N = 100000;

//輸入
int n;
int s;//環的起點 
vector<int> G[Max_N+1];//
vector<int> V;//記錄環
bool visited[Max_N+1];//結點是否訪問過(避免重複訪問) 

int par[Max_N+1];//並查集
int rank[Max_N+1];//並查集高度

//初始化並查集
void init(int n)
{
    for(int i=1; i<=n; i++){//剛開始互相沒有邊相連 
        par[i] = i;
    }    
} 

//找下標x的根 
int find(int x)
{
    return x==par[x] ? x:par[x] = find(par[x]);//par[x] = find(par[x])即路徑壓縮 
}

//合併
void unite(int x,int y)
{
    x = find(x);    y=find(y);//先找到他們的根
    
    if( x==y ){
        return;//同根 直接返回 
    }    
    
    if( rank[x]<rank[y] ){
        par[x] = y;//將x所在的樹合併在y下 
    }
    else
    {
        par[y] = x;
        if( rank[x]==rank[y] ){//若是高度相同 更新rank[x] 
            rank[x]++;
        }
    }
} 

//x,y是否在同一組 便是否同根 
bool same(int x,int y)
{
    return find(x)==find(y);    
} 
//dfs 
bool dfs(int u)
{
    if( visited[u] )//若是最後訪問的結點回到起點,則在環內 
    {
        if( u==s )
        {
            return true;
        }
        return false;//不然不在環內 
    }
    
    visited[u] = true;//u已經訪問過 
    
    for(int i=0; i<G[u].size(); i++)//遍歷頂點 u 的相鄰邊 
    {
        int v = G[u][i];//下一個頂點
        if( dfs(v) )//在環內 
        {
            V.push_back(v);
            return true;    
        } 
    }
    return false;//遍歷全部邊都不在環內 
} 

void solve()
{
    dfs(s);
    
    sort(V.begin(),V.end());
    
    vector<int>::iterator it;
    for( it=V.begin(); it!=V.end(); it++)
    {
        printf("%d ",*it);
    }
    printf("\n");
}

int main()
{
    scanf("%d",&n);
    init(n);
    while( n-- )
    {
        int u,v;
        scanf("%d%d",&u,&v);
        G[u].push_back(v);    G[v].push_back(u);
        if( same(u,v) ){//u,v已經有路徑了 
            s = u;//起點下標 
        }
        unite(u,v);
    }
    
    solve();
    
    return 0;
}
相關文章
相關標籤/搜索