資料結構(17)--圖的遍歷DFS和BFS
參考書籍:資料結構(C語言版)嚴蔚敏吳偉民編著清華大學出版社
從圖中某一頂點出發訪遍圖中其餘頂點,且使每一個頂點僅被訪問一次。這一過程就叫做圖的遍歷。
示例:
1.深度優先遍歷
基本思想:
從圖中某頂點V0出發,訪問此頂點,然後依次從V0的各個未被訪問的鄰接點出發深度優先搜尋遍歷圖,直至圖中所有和V0有路徑相通的頂點都被訪問到;
若此時圖中尚有頂點未被訪問,則另選圖中一個未曾被訪問的頂點作起始點;
重複上述過程,直至圖中所有頂點都被訪問到為止。
分析:
在遍歷圖時,對圖中每個頂點至多呼叫一次DFS函式,因為一旦某個頂點被標誌成已被訪問,就不再從它出發進行搜尋。
因此,遍歷圖的過程實質上是對每個頂點查詢其鄰接點的過程。其耗費的時間則取決於所採用的儲存結構。 當使用二維陣列表示鄰接矩陣作圖的儲存結構時,查詢每個頂點的鄰接點所需時間為O(n^2)
程式碼實現:
int visited[MAX_VERTEX_NUM];//訪問標識陣列
void DFS(ALGraph G, int ivex){ //從第i個頂點出發遞迴的深度優先遍歷圖G visited[ivex] = 1; printf("v%d ", G.vexs[ivex].data);//列印(訪問)該頂點 for(ArcNode *p = G.vexs[ivex].firstarc; p; p = p->nextarc){//對於第ivex個頂點的每個未被訪問的鄰接點遞迴呼叫DFS if(!visited[p->adjvex]){ DFS(G, p->adjvex); } } }
//深度優先遍歷無向圖G(相當於樹的先序遍歷)(遞迴演算法) void DFSTraverseGraph(ALGraph G){ //初始化訪問標誌陣列 for(int i = 0; i < G.vexnum; i++){ visited[i] = 0;//0表示未被訪問,1表示已被訪問 } printf("請輸入遍歷的起始頂點(如:v1):"); VertexType startVex; scanf("v%d", &startVex); int startVexPos = locateVex(G, startVex); printf("一條深度優先遍歷序列為:"); if(!visited[startVexPos]) DFS(G, startVexPos); printf("\n"); /* for(i = 0; i < G.vexnum; i++){//圖中每個頂點至多呼叫一次DFS函式 if(!visited[i]){//對還未訪問過的頂點呼叫DFS DFS(G, i); } } */ }
//深度優先遍歷無向圖G(相當於樹的先序遍歷)(非遞迴演算法)
void DFSTraverseGraph2(ALGraph G){
int stack[MAX_VERTEX_NUM];//維護一個棧來儲存訪問圖的頂點的(位置)資訊
int top = 0;//初始化棧頂指標,為空棧
//初始化訪問標誌陣列
for(int i = 0; i < G.vexnum; i++){
visited[i] = 0;//0表示未被訪問,1表示已被訪問
}
printf("請輸入遍歷的起始頂點(如:v1):");
VertexType startVex;
scanf("v%d", &startVex);
int startVexPos = locateVex(G, startVex);
printf("一條深度優先遍歷序列為:");
ArcNode *p;// = G.vexs[startVexPos].firstarc;
int ivex = startVexPos;
while(!visited[ivex] || top!=-1){//棧不為空
if(!visited[ivex]){//第vex結點沒有被訪問過
visited[ivex] = 1;
printf("v%d ", G.vexs[ivex].data);
stack[top++] = ivex;
}
p = G.vexs[ivex].firstarc;
while(p && visited[p->adjvex])//p不為空且p已經被訪問過,就跳過
p = p->nextarc;
//此時p指向以當前頂點為頭的且未被訪問第一個尾頂點
if(p){//如果p不為空
ivex = p->adjvex;
}else{//如果p為空,說明當前頂點的所有和他有路徑相通的頂點均已訪問,則棧頂元素出棧,查詢下一個尚未被訪問的頂點
ivex = stack[--top];//棧頂元素出棧
}
}
printf("\n");
}
2.廣度優先遍歷
基本思想:
從圖中某個頂點V0出發,並在訪問此頂點後依次訪問V0的所有未被訪問過的鄰接點,之後按這些頂點被訪問的先後次序依次訪問它們的鄰接點,直至圖中所有和V0有路徑相通的頂點都被訪問到;
若此時圖中尚有頂點未被訪問,則另選圖中一個未曾被訪問的頂點作起始點;
重複上述過程,直至圖中所有頂點都被訪問到為止。
分析:
每個頂點至多進一次佇列。遍歷圖的過程實質上是通過邊或弧找鄰接點的過程,因此廣度優先搜尋遍歷圖的時間複雜度和深度優先搜尋遍歷相同,兩者不同之處僅僅在於對頂點訪問的順序不同。
程式碼實現:
int visited[MAX_VERTEX_NUM];//訪問標識陣列
//廣度優先遍歷無向圖G(相當於樹的按層次遍歷)(非遞迴演算法)
void BFSTraverseGraph(ALGraph G){
int queue[MAX_VERTEX_NUM];//維護一個佇列來儲存訪問圖的頂點的(位置)資訊
int front = 0, rail = 0;//初始化隊頭、隊尾指標,為空佇列
//初始化訪問標誌陣列
for(int i = 0; i < G.vexnum; i++){
visited[i] = 0;//0表示未被訪問,1表示已被訪問
}
printf("請輸入遍歷的起始頂點(如:v1):");
VertexType startVex;
scanf("v%d", &startVex);
int startVexPos = locateVex(G, startVex);
printf("一條廣度優先遍歷序列為:");
queue[rail++] = startVexPos;//起點先入隊
int ivex;// = startVexPos;
ArcNode *p;
while(front != rail){//不是空佇列
ivex = queue[front++];//隊頭元素出隊
if(!visited[ivex]){
visited[ivex] = 1;
printf("v%d ", G.vexs[ivex].data);
}
p = G.vexs[ivex].firstarc;
while(p){//p指向與ivex的鄰接的(同一個層次的)還未被頂點
if(!visited[p->adjvex])
queue[rail++] = p->adjvex;//入隊
p = p->nextarc;
}
}
printf("\n");
}
3.演示
//以無向圖的鄰接表作為儲存結構,實現圖深度優先遍歷演算法
#include<stdio.h>
#include<stdlib.h>
/*
圖的表示方法
DG(有向圖)或者DN(有向網):鄰接矩陣、鄰接表(逆鄰接表--為求入度)、十字連結串列
UDG(無向圖)或者UDN(無向網):鄰接矩陣、鄰接表、鄰接多重表
*/
#define MAX_VERTEX_NUM 10//最大頂點數目
#define NULL 0
//typedef int VRType;//對於帶權圖或網,則為相應權值
typedef int VertexType;//頂點型別
//typedef enum GraphKind {DG, DN, UDG, UDN}; //有向圖:0,有向網:1,無向圖:2,無向
typedef struct ArcNode{
int adjvex;//該弧所指向的頂點的在圖中位置
//VRType w;//弧的相應權值
struct ArcNode *nextarc;//指向下一條邊的指標
}ArcNode;//弧結點資訊
typedef struct VNode{
VertexType data;//頂點資訊
ArcNode *firstarc;//指向第一條依附該頂點的弧的指標
}VNode, AdjVexList[MAX_VERTEX_NUM];//頂點結點資訊
typedef struct{
AdjVexList vexs;//頂點向量
int vexnum, arcnum;//圖的當前頂點數和弧數
//GraphKind kind;//圖的種類標誌
}ALGraph;//鄰接表表示的圖
//若圖G中存在頂點v,則返回v在圖中的位置資訊,否則返回其他資訊
int locateVex(ALGraph G, VertexType v){
for(int i = 0; i < G.vexnum; i++){
if(G.vexs[i].data == v)
return i;
}
return -1;//圖中沒有該頂點
}
//採用鄰接表表示法構造無向圖G
void createUDN(ALGraph &G){
printf("輸入頂點數和弧數如:(5,3):");
scanf("%d,%d", &G.vexnum, &G.arcnum);
//構造頂點向量,並初始化
printf("輸入%d個頂點(以空格隔開如:v1 v2 v3):", G.vexnum);
getchar();//吃掉換行符
for(int m = 0; m < G.vexnum; m++){
scanf("v%d", &G.vexs[m].data);
G.vexs[m].firstarc = NULL;//初始化為空指標////////////////重要!!!
getchar();//吃掉空格符
}
//構造鄰接表
VertexType v1, v2;//分別是一條弧的弧尾和弧頭(起點和終點)
//VRType w;//對於無權圖或網,用0或1表示相鄰否;對於帶權圖或網,則為相應權值
printf("\n每行輸入一條弧依附的頂點(先弧尾後弧頭)(如:v1v2):\n");
fflush(stdin);//清除殘餘後,後面再讀入時不會出錯
int i = 0, j = 0;
for(int k = 0; k < G.arcnum; k++){
scanf("v%dv%d",&v1, &v2);
fflush(stdin);//清除殘餘後,後面再讀入時不會出錯
i = locateVex(G, v1);//弧起點
j = locateVex(G, v2);//弧終點
//採用“頭插法”在各個頂點的弧鏈頭部插入弧結點
ArcNode *p1 = (ArcNode *)malloc(sizeof(ArcNode));//構造一個弧結點,作為弧vivj的弧頭(終點)
p1->adjvex = j;
//p1->w = w;
p1->nextarc = G.vexs[i].firstarc;
G.vexs[i].firstarc = p1;
ArcNode *p2 = (ArcNode *)malloc(sizeof(ArcNode));//構造一個弧結點,作為弧vivj的弧尾(起點)
p2->adjvex = i;
//p2->w = w;
p2->nextarc = G.vexs[j].firstarc;
G.vexs[j].firstarc = p2;
}
}
/*測試:8,9
v1 v2 v3 v4 v5 v6 v7 v8 v9
v1v2
v1v3
v2v4
v2v5
v3v6
v3v7
v4v8
v5v8
v6v7
*/
void main(){
ALGraph G;
createUDN(G);
//printAdjList(G);
printf("\n深度優先遍歷(遞迴演算法):\n");
DFSTraverseGraph(G);
fflush(stdin);//清除殘餘後,後面再讀入時不會出錯
printf("\n深度優先遍歷(非遞迴演算法):\n");
DFSTraverseGraph2(G);
fflush(stdin);//清除殘餘後,後面再讀入時不會出錯
printf("\n廣度優先遍歷(非遞迴演算法):\n");
BFSTraverseGraph(G);
}