怎麼判斷圖有環?

因爲疫情在家,沒事情幹,腦子裏突然迸發出了一個想法,圖是程序員非常熟悉的數據結構,而且也被廣泛地應用與生活日常,機器人,航天工程等各種領域。但是如果我們如果只是圍着一個圖去繞圈,那可能沒什麼意義。而且會陷入迷宮當中出不來。所以就想寫一寫,如何判斷 圖到底有沒有環呢?

我們以有向圖爲例(假設圖肯定是連通的):

                              圖1 、有向無環圖                                                         圖2 、有向有環圖

 

一、dfs迷宮式走法:

如果我們通過普通的dfs對圖1進行 遍歷時候,我們可能得到的結果的 :A-->B-->E-->C-->F-->D

僞代碼:

struct graph{
    int to;
    int weight;
};
vector<graph> g[n];//鄰接表的圖結構
bool visit[n]={false};
void dfs(int s){
    visit[s]=true; //設置s頂點已經被訪問過
    //輸出s
    for(int i=0;i<g[s].size();i++){ //訪問s的鄰接點
        if(!visit[g[s][i].to])
            dfs(g[s][i].to);
    }

}

當我們使用如上代碼去遍歷圖2時,經過大腦的運算應該是:A-->B-->E-->C-->F-->D

emmm,雖然路徑一樣,但是程序猿er肯定都知道他走的路線是不一樣的。

這都不重要,至關重要的是:這nm不能看出有沒有環啊。。。。

但是我們只要將代碼稍微改動一下就可以判斷了。

那稍微改動,究竟要改哪兒呢,,,我們先分析一下爲什麼上面的dfs不能判斷是否有環,因爲我們維護了一個數組visit,這個數組用來存儲點有沒有被訪問的狀態。如果點被訪問過,visit[點]=true,所以即便是有環也不會再次訪問了。

如上圖,我們從A點出發,到達C點後,因爲A已經被標記,所以不會再次訪問A,也就無法判斷圖是否有環

所以通過分析我們發現,我們只要改一下,visit這個數組(也就是存儲點狀態的這個數組就可以解決問題)。

那麼我們要怎麼設計呢?

上述的visit有兩個狀態(訪問過和未訪問過),如果我們擴展一個狀態是不是就好了呢?

如果把visit改成三維的(訪問過、未訪問過、正在訪問中)。

struct graph{
    int to;
    int weight;
};
vector<graph> g[n];//鄰接表的圖結構
int visit[n]={-1};//-1表示未訪問過
bool dfs(int s){
    visit[s]=0; //設置s頂點正在被訪問
    //輸出s
    for(int i=0;i<g[s].size();i++){ //訪問s的鄰接點
        if(visit[g[s][i].to]==0){//如果正在訪問的點又被訪問到則代表有環
            return false;
        }
        else if(visit[g[s][i].to]==-1){
            return dfs(g[s][i].to);
        }   
    }
    visit[s]=1;//s結點訪問完畢

}

通過這個方法,一開始我也是 半明白半糊塗,但是隻要按照這個方法,跑一下上面的圖,我們就會發現這個方法的神奇。

如果轉過一圈又回到了這個點,那麼就說明有環。

 

二、拓撲排序法

我們可以在圖的初始化以及插入邊的時候,確定每個頂點的入度

我們可以把入度看作是 一件事情的完成順序。

下圖中A和B的入度都是0,表示A,B沒有前提事件,而C的入讀是2,也就是必須A和B完成之後才能完成C。

那麼我們怎麼通過這種方法來判斷圖中是否有環呢?

還以這幅圖爲例,我們可以看到,A,B,C三點的入度都是1。沒有入度爲0的點,所以這三件事情互相爲前提,以至於這三件事情都不能完成。這就判斷出了環。

如果更復雜一點兒的圖呢

如上圖,我們可以這麼看,S的入度爲0,所以我們可以先把S完成,S完成後刪除這個節點,此時剩下的ABC三個節點中入度都是0,又陷入了剛纔之前的循環中。所以此圖有環。轉換爲僞代碼就是:

int indegree[n]={0};//在圖初始化或者插入邊時記錄入度
int visit[n]={0};
void is_circle(){
    while(存在入讀爲0的點){
        //找出圖中入度爲0的點
        //把visit[入度爲0的點]設爲1,表示已經訪問過
        for(;;){
            //遍歷這個點的鄰接點,把他們的入度 減一
        }
    }
    //如果出循環說明不存在入讀爲0的點。
    //那麼看visit是否都訪問過。如果有節點未訪問過。說明圖中有環。
}

 

 

emmm,這就是我想到的兩個方法,判斷的方法肯定有很多。以後 慢慢思考哈哈哈

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