1. 程式人生 > >資料結構(17)--圖的遍歷DFS和BFS

資料結構(17)--圖的遍歷DFS和BFS

參考書籍:資料結構(C語言版)嚴蔚敏吳偉民編著清華大學出版社

    從圖中某一頂點出發訪遍圖中其餘頂點,且使每一個頂點僅被訪問一次。這一過程就叫做圖的遍歷

示例:


1.深度優先遍歷

基本思想:
    從圖中某頂點V0出發,訪問此頂點,然後依次從V0的各個未被訪問的鄰接點出發深度優先搜尋遍歷圖,直至圖中所有和V0有路徑相通的頂點都被訪問到;
    若此時圖中尚有頂點未被訪問,則另選圖中一個未曾被訪問的頂點作起始點;
    重複上述過程,直至圖中所有頂點都被訪問到為止。

分析:

    在遍歷圖時,對圖中每個頂點至多呼叫一次DFS函式,因為一旦某個頂點被標誌成已被訪問,就不再從它出發進行搜尋。
    因此,遍歷圖的過程實質上是對每個頂點查詢其鄰接點的過程。其耗費的時間則取決於所採用的儲存結構。 當使用二維陣列表示鄰接矩陣作圖的儲存結構時,查詢每個頂點的鄰接點所需時間為O(n^2)

,其中n為頂點數。而當以鄰接表作圖的儲存結構時,找鄰接點所需時間為O(e),其中e為無向圖中邊的數目或有向圖中弧的數目。由此,當以鄰接表作儲存結構時,深度優先搜遍遍歷圖的時間複雜度為O(n+e)

程式碼實現:

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);
}