參考書籍:數據結構(C語言版)嚴蔚敏吳偉民編著清華大學出版社
本文中的代碼可從這裏下載:https://github.com/qingyujean/data-structure
從圖中某一頂點出發訪遍圖中其餘頂點,且使每一個頂點僅被訪問一次。這一過程就叫做圖的遍歷。
示例:
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);
}