因爲疫情在家,沒事情幹,腦子裏突然迸發出了一個想法,圖是程序員非常熟悉的數據結構,而且也被廣泛地應用與生活日常,機器人,航天工程等各種領域。但是如果我們如果只是圍着一個圖去繞圈,那可能沒什麼意義。而且會陷入迷宮當中出不來。所以就想寫一寫,如何判斷 圖到底有沒有環呢?
我們以有向圖爲例(假設圖肯定是連通的):
圖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,這就是我想到的兩個方法,判斷的方法肯定有很多。以後 慢慢思考哈哈哈