資料結構與演算法18-圖的遍歷
從圖中某一個頂點出發訪遍圖中其餘頂點,且使每個頂點權被訪問一次,這一過程就叫做圖的遍歷(Traversing Graph)。
深度優先遍歷(Depth_First_Search)
從圖中某個頂點v出發,訪問此頂點,然後從v的未被訪問的鄰接點出發深度優先遍歷圖,直至圖中所有和v有路徑想通的頂點都被訪問到。
事實上,我們這裡講到的是連通圖,對於非連通圖,只需要對它的連通分量分別進行深度優先遍歷,即在前一個頂點進行一次深度優先遍歷後,若圖中尚有頂點未被訪問,則另選圖中一個未曾被訪問的頂點作起始點,重複上述過程,直至圖中所有頂點都補充訪問到為止。
如果我們用的是鄰接矩陣方式,則程式碼如下:
typedef int Boolean; Boolean visited[MAX]; /*鄰接矩陣的深度優先遞迴演算法*/ void DFS(MGraph G,int i) { int j; visited[i] = TRUE; printf(“%c”,G.vexs[i]); for(j=0;j<G.numVertexes;j++) if(G.arc[i][j]==1 && !visited[j]) DFS(G,j); //對訪問的鄰接頂點遞迴呼叫。 } /*鄰接矩陣的深度遍歷操作*/ void DFSTravese(MGraph G) { int i; for(i=0;i<G.numVertexes;i++) visited[i]=FALSE; //初始化訪問狀態為未訪問 for(i=0;i<G.numVertexes;i++) if(!visited[!]) DFS(G,i); }
如果結構圖是鄰接表結構,其DFSTraverse函式的程式碼是幾乎相同的,只是在遞迴函式中因為將陣列換成連結串列而不同
程式碼如下:
/*鄰接連結串列的深度優先遞迴演算法*/ void DFS(GrapAdjList GL,int i) { EdgeNode *p; visited[i] = true; printf(“%c”,GL->adjList[i].data); //列印頂點 p=GL->adjList[i].firstedge; while(p) { if(!visited[p->adjvex]) DFS(GL,p-adjvex); p=p->next; } } /*鄰接表的深度遍歷操作*/ void DFSTraverse(GraphAdjList GL) { int i; for(i=0;i<GL->numVertexes;i++) visited[i] = FALSE; for(i=0;i<GL->numVertexes;i++) if(!visited[i]) DFS(GL,i); }
對比丙從此不同儲存結構的深度優先遍歷演算法,對於n個頂點e條邊的圖來說,鄰接矩陣由於是二維陣列,要查詢每個頂點的鄰接點需要訪問矩陣中的怕有元素,因此需要O(n2)的時間。而鄰接表做的儲存結構時,找鄰接點的所需的時間取決於頂點和邊的數量,所以是O(n+e)顯然對於點多邊少的稀疏圖來說,鄰接表結構使得演算法在時間效率上大大提高。
對於有向圖而非言,由於它只是對通道存在可行或不可行,演算法上沒有變化,是完全可以通用的。
廣度優先遍歷
又稱為廣度優先搜尋,簡稱BFS。
如果說圖的深度優先遍歷類似樹的前序遍歷,那麼圖的廣度優先遍歷類似於樹的層序遍歷。
如圖的圖稍微變形,變形原則是頂點A放置在最上第一層,讓與它有邊的頂點B、F為第二層,再讓與BF有邊的頂點C、I、G、E為第三層,再將這四個頂點的邊的D、H放在第四層
鄰接矩陣的廣度優先遍歷演算法
void SFSTraverse(MGraph G)
{
int i,j;
Queue Q;
for(i=0;i<G.numVertexes;i++)
visited[i] = FALSE;
InitQueue(&Q);
for(i=0;i<G.numVertexes;i++) //對第一個頂點做迴圈
{
if(!visited[i])
{
visited[i] = TRUE;
printf(“%c”,G.vexs[i]); //列印頂點,也可以其他操作
EnQueue(&Q,&i); //將此頂點入佇列
while(!QueueEmpty(Q))
{
//佇列不為空
DeQueue(&Q,&i); //待隊中元素出隊,賦值給i
for(j=0;j<G.numVertexes;j++)
{ //判斷其它頂點和當前頂點存在邊且未訪問過
if(G.arc[i][j]==1 &&!visited[j])
{
visited[j] = TRUE;
printf(“%c”,G.vexs[j]);
EnQueue(&Q,j); //將找到的此頂點入佇列
}
}
}
}
}
鄰接表的廣度遍歷演算法
void BFSTraverse(GraphAdjList GL)
{
int i;
EdgeNode *p;
Queue Q;
for(i=0;i<GL->numVertexes;i++)
visited[i] = FALSE;
InitQueue(&Q);
for(i=0;i<GL->numVertexes;i++)
{
if(!visited[i])
{
visited[i]=TRUE;
printf(“%c”,GL->adjList[i].data);
EnQueue(&Q,i);
while(!QueueEmpty(Q))
{
DeQueue(&Q,&i);
p=GL->adjList[i].firstedge; //找到當前頂點邊錶鏈表頭指標
while(p)
{
if(!visited[p->adjvex])
{
visited[p->adjvex] = TRUE;
printf(“%c”,GL->adjList[p->adjvex].data);
EnQueue(&Q,p->adjvex);
}
p =p->next;
}
}
}
}
}
對比的圖的深度優先遍歷與廣度優先遍歷演算法,你會發現,它們在時間複雜度上是一樣的,不同之處僅僅在對於頂點訪問的順序不同。可見兩者在全圖遍歷上是沒有優劣之分的,只是視不同的情況選擇不同的演算法。