圖的遍曆概念
1、圖的遍曆
和樹的遍曆類似,圖的遍曆也是從某個頂點出發,沿著某條搜尋路徑對圖中每個頂點各做一次且僅做一次訪問。它是許多圖的演算法的基礎。
深度優先遍曆和廣度優先遍曆是最為重要的兩種遍曆圖的方法。它們對無向圖和有向圖均適用。
注意:
以下假定遍曆過程中訪問頂點的操作是簡單地輸出頂點。
2、布爾向量visited[0..n-1]的設定
圖中任一頂點都可能和其它頂點相鄰接。在訪問了某頂點之後,又可能順著某條迴路又回到了該頂點。為了避免重複訪問同一個頂點,必須記住每個已訪問的頂點。為此,可設一布爾向量visited[0..n-1],其初值為假,一旦訪問了頂點V i之後,便將visited[i]置為真。
深度優先遍曆(Depth-First Traversal)
1.圖的深度優先遍曆的遞迴定義
假設給定圖G的初態是所有頂點均未曾訪問過。在G中任選一頂點v為初始出發點(源點),則深度優先遍曆可定義如下:首先訪問出發點v,並將其標記為已訪問過;然後依次從v出發搜尋v的每個鄰接點w。若w未曾訪問過,則以w為新的出發點繼續進行深度優先遍曆,直至圖中所有和源點v有路徑相通的頂點(亦稱為從源點可達的頂點)均已被訪問為止。若此時圖中仍有未訪問的頂點,則另選一個尚未訪問的頂點作為新的源點重複上述過程,直至圖中所有頂點均已被訪問為止。
圖的深度優先遍曆類似於樹的前序走訪。採用的搜尋方法的特點是儘可能先對縱深方向進行搜尋。這種搜尋方法稱為深度優先搜尋(Depth-First Search)。相應地,用此方法遍曆圖就很自然地稱之為圖的深度優先遍曆。
2、深度優先搜尋的過程
設x是當前被訪問頂點,在對x做過訪問標記後,選擇一條從x出發的未檢測過的邊(x,y)。若發現頂點y已訪問過,則重新選擇另一條從x出發的未檢測過的邊,否則沿邊(x,y)到達未曾訪問過的y,對y訪問並將其標記為已訪問過;然後從y開始搜尋,直到搜尋完從y出發的所有路徑,即訪問完所有從y出發可達的頂點之後,才回溯到頂點x,並且再選擇一條從x出發的未檢測過的邊。上述過程直至從x出發的所有邊都已檢測過為止。此時,若x不是源點,則回溯到在x之前被訪問過的頂點;否則圖中所有和源點有路徑相通的頂點(即從源點可達的所有頂點)都已被訪問過,若圖G是連通圖,則遍曆過程結束,否則繼續選擇一個尚未被訪問的頂點作為新源點,進行新的搜尋過程。
3、深度優先遍曆的遞迴演算法
(1)深度優先遍曆演算法
typedef enum{FALSE,TRUE}Boolean;//FALSE為0,TRUE為1
Boolean visited[MaxVertexNum]; //訪問標誌向量是全域量
void DFSTraverse(ALGraph *G)
{ //深度優先遍曆以鄰接表表示的圖G,而以鄰接矩陣表示G時,演算法完全與此相同
int i;
for(i=0;i<G->n;i++)
visited[i]=FALSE; //標誌向量初始化
for(i=0;i<G->n;i++)
if(!visited[i]) //vi未訪問過
DFS(G,i); //以vi為源點開始DFS搜尋
}//DFSTraverse
(2)鄰接表表示的深度優先搜尋演算法
void DFS(ALGraph *G,int i){
//以vi為出發點對鄰接表表示的圖G進行深度優先搜尋
EdgeNode *p;
printf("visit vertex:%c",G->adjlist[i].vertex);//訪問頂點vi
visited[i]=TRUE; //標記vi已訪問
p=G->adjlist[i].firstedge; //取vi邊表的頭指標
while(p){//依次搜尋vi的鄰接點vj,這裡j=p->adjvex
if (!visited[p->adjvex])//若vi尚未被訪問
DFS(G,p->adjvex);//則以Vj為出發點向縱深搜尋
p=p->next; //找vi的下一鄰接點
}
}//DFS
(3)鄰接矩陣表示的深度優先搜尋演算法
void DFSM(MGraph *G,int i)
{ //以vi為出發點對鄰接矩陣表示的圖G進行DFS搜尋,設鄰接矩陣是0,l矩陣
int j;
printf("visit vertex:%c",G->vexs[i]);//訪問頂點vi
visited[i]=TRUE;
for(j=0;j<G->n;j++) //依次搜尋vi的鄰接點
if(G->edges[i][j]==1&&!visited[j])
DFSM(G,j)//(vi,vj)∈E,且vj未訪問過,故vj為新出發點
}//DFSM
注意:
遍曆操作不會修改圖G的內容,故上述演算法中可將G定義為ALGraph或MGraph類型的參數,而不一定要定義為ALGraph和MGraph的指標類型。但基於效率上的考慮,選擇指標類型的參數為宜。
4、深度優先遍曆序列
對圖進行深度優先遍曆時,按訪問頂點的先後次序得到的頂點序列稱為該圖的深度優先遍曆序列,或簡稱為DFS序列。
(1)一個圖的DFS序列不一定惟一
當從某頂點x出發搜尋時,若x的鄰接點有多個尚未訪問過,則我們可任選一個訪問之。
(2)源點和儲存結構的內容均已確定的圖的DFS序列惟一
① 鄰接矩陣表示的圖確定源點後,DFS序列惟一
DFSM演算法中,當從vi出發搜尋時,是在鄰接矩陣的第i行上從左至右選擇下一個未曾訪問過的鄰接點作為新的出發點,若這樣的鄰接點多於一個,則選中的總是序號較小的那一個。
【例】對連通圖G7用鄰接矩陣表示時,以v0為初始出發點的 DFS遍曆過程如下圖(a)所示,具體演算法的執行過程【參見動畫示範】。DFS序列是v0,v1,v2,v5,v4,v6,v3,v7
②只有給出了鄰接表的內容及初始出發點,才能惟一確定其DFS序列
鄰接表作為給定圖的儲存結構時,其表示不惟一。因為鄰接表上邊表裡的鄰接點域的內容與建表時的輸入次序相關。
因此,只有給出了鄰接表的內容及初始出發點,才能惟一確定其DFS序列。
【例】連通圖G7用鄰接表表示時,以v0為初始出發點的 DFS演算法的執行過程和DFS序列【參見動畫示範】
(3)棧在深度優先遍曆演算法中的作用
深度優先遍曆過程中,後訪問的頂點其鄰接點被先訪問,故在遞迴調用過程中使用棧(系統運行時刻棧)來儲存已訪問的頂點。
5、演算法分析
對於具有n個頂點和e條邊的無向圖或有向圖,遍曆演算法DFSTraverse對圖中每頂點至多調用一次DFS或DFSM。從DFSTraverse中調用DFS(或DFSM)及DFS(或DFSM)內部遞迴調用自己的總次數為n。
當訪問某頂點vi時,DFS(或DFSM)的時間主要耗費在從該頂點出發搜尋它的所有鄰接點上。用鄰接矩陣表示圖時,其搜尋時間為O(n);用鄰接表表示圖時,需搜尋第i個邊表上的所有結點。因此,對所有n個頂點訪問,在鄰接矩陣上共需檢查n2個矩陣元素,在鄰接表上需將邊表中所有O(e)個結點檢查一遍。
所以,DFSTraverse的時間複雜度為O(n2) (調用DFSM)或0(n+e)(調用DFS)。