【演算法導論】圖的廣度優先搜尋遍歷(BFS)
阿新 • • 發佈:2019-01-10
圖的儲存方法:鄰接矩陣、鄰接表
例如:有一個圖如下所示(該圖也作為程式的例項):
則上圖用鄰接矩陣可以表示為:
用鄰接表可以表示如下:
鄰接矩陣可以很容易的用二維陣列表示,下面主要看看怎樣構成鄰接表:
鄰接表儲存方法是一種順序儲存與鏈式儲存相結合的儲存方法。在這種方法中,只考慮非零元素,所以在圖中的頂點很多而邊很少時,可以節省儲存空間。
鄰接表儲存結構由兩部分組成:對於每個頂點vi, 使用一個具有兩個域的結構體陣列來儲存,這個陣列稱為頂點表。其中一個域稱為頂點域(vertex),用來存放頂點本身的資料資訊;而另一個域稱為指標域(link),用來存放依附於該頂點的邊所組成的單鏈表的表頭結點的儲存位置。鄰接於vi的頂點vj連結成的單鏈表稱為vi的鄰接連結串列。鄰接連結串列中的每個結點由兩個域構成:一是鄰接點域(adjvex),用來存放與vi相鄰接的頂點vj的序號j (可以是頂點vj在頂點表中所佔陣列單元的下標);
其二是鏈域(next),用來將鄰接連結串列中的結點連結在一起。具體的程式實現如下:
void CreateAdjTable(vexnode ga[N],int e)//建立鄰接表 { int i,j,k; edgenode *s; printf("\n輸入頂點的內容:"); for(i=0;i<N;i++) { ga[i].vertex=getchar();//讀入頂點的內容 ga[i].link=NULL;//初始化 } printf("\n"); for(k=0;k<e;k++) { printf("輸入邊的兩個頂點的序號:"); scanf("%d%d",&i,&j);//讀入邊的兩個頂點的序號 s=(edgenode *)malloc(sizeof(edgenode)); s->adjvex=j; s->next=ga[i].link; ga[i].link=s; s=(edgenode *)malloc(sizeof(edgenode)); s->adjvex=i; s->next=ga[j].link; ga[j].link=s; } }
廣度優先搜尋遍歷(BFS):
圖的廣度優先搜尋遍歷類似於樹的按層次遍歷。在假設初始狀態是圖中所有頂點都未被訪問的條件下,這種方法從圖中某一頂點vi出發,先訪問vi,然後訪問vi的鄰接點vj。在所有的vj都被訪問之後,再訪問vj的鄰接點vk,依次類推,直到圖中所有和初始出發點vi有路徑相通的頂點都被訪問為止。若圖是非連通的,則選擇一個未曾被訪問的頂點作為起始點,重複以上過程,直到圖中所有頂點都被訪問為止。在這種方法的遍歷過程中,先被訪問的頂點,其鄰接點也先被訪問,具有先進先出的特性,所以可以使用一個佇列來儲存已訪問過的頂點,以確定對訪問過的頂點的鄰接點的訪問次序。為了避免重複訪問一個頂點,也使用了一個輔助陣列visited[n]來標記頂點的訪問情況。下面分別給出以鄰接矩陣和鄰接表為儲存結構時的廣度優先搜尋遍歷演算法BFS_matrix和BFS_AdjTable:
具體程式實現如下:
#include<stdio.h>
#include<stdlib.h>
#define N 5
//鄰接矩陣儲存法
typedef struct
{
char vexs[N];//頂點陣列
int arcs[N][N];
}graph;
//鄰接表儲存法
typedef struct Node
{
int adjvex;
struct Node *next;
}edgenode;
typedef struct
{
char vertex;
edgenode *link;
}vexnode;
//佇列操作
typedef struct node
{
int data;
struct node *next;
}linklist;
typedef struct
{
linklist *front,*rear;
}linkqueue;
void SetNull(linkqueue *q)//佇列置空
{
q->front=(linklist *)malloc(sizeof(linklist));
q->front->next=NULL;
q->rear=q->front;
}
int Empty(linkqueue *q)
{
if(q->front==q->rear)
return 1;
else
return 0;
}
int Front(linkqueue *q)//取隊頭元素
{
if(Empty(q))
{
printf("queue is empty!");
return -1;
}
else
return q->front->next->data;
}
void ENqueue(linkqueue *q,int x)//入隊
{
linklist * newnode=(linklist *)malloc(sizeof(linklist));
q->rear->next=newnode;
q->rear=newnode;
q->rear->data=x;
q->rear->next=NULL;
}
int DEqueue(linkqueue *q)//出隊
{
int temp;
linklist *s;
if(Empty(q))
{
printf("queue is empty!");
return -1;
}
else
{
s=q->front->next;
if(s->next==NULL)
{
q->front->next=NULL;
q->rear=q->front;
}
else
q->front->next=s->next;
temp=s->data;
return temp;
}
}
void BFS_matrix(graph g,int k,int visited[N])//圖按照鄰接矩陣儲存時的廣度優先遍歷
{
int i=0;
linkqueue q;
SetNull(&q);
printf("%c\n",g.vexs[k]);
visited[k]=1;
ENqueue(&q,k);
while(!Empty(&q))
{
i=DEqueue(&q);
for(int j=0;j<N;j++)
{
if(g.arcs[i][j]==1&&visited[j]!=1)
{
printf("%c\n",g.vexs[j]);
visited[j]=1;
ENqueue(&q,j);
}
}
}
}
void CreateAdjTable(vexnode ga[N],int e)//建立鄰接表
{
int i,j,k;
edgenode *s;
printf("\n輸入頂點的內容:");
for(i=0;i<N;i++)
{
ga[i].vertex=getchar();//讀入頂點的內容
ga[i].link=NULL;//初始化
}
printf("\n");
for(k=0;k<e;k++)
{
printf("輸入邊的兩個頂點的序號:");
scanf("%d%d",&i,&j);//讀入邊的兩個頂點的序號
s=(edgenode *)malloc(sizeof(edgenode));
s->adjvex=j;
s->next=ga[i].link;
ga[i].link=s;
s=(edgenode *)malloc(sizeof(edgenode));
s->adjvex=i;
s->next=ga[j].link;
ga[j].link=s;
}
}
void BFS_AdjTable(vexnode ga[],int k,int visited[N])//圖按照鄰接表儲存時的廣度優先遍歷
{
int i=0;
edgenode *p;
linkqueue q;
SetNull(&q);
printf("%c\n",ga[k].vertex);
visited[k]=1;//標記是否被訪問過
ENqueue(&q,k);//入隊
while(!Empty(&q))
{
i=DEqueue(&q);
p=ga[i].link;
while(p!=NULL)
{
if(visited[p->adjvex]!=1)
{
printf("%c\n",ga[p->adjvex].vertex);
visited[p->adjvex]=1;
ENqueue(&q,p->adjvex);
}
p=p->next;
}
}
}
void main()
{
graph g;
vexnode ga[N];
int visited[5]={0};
int visited1[5]={0};
g.vexs[0]='A';
g.vexs[1]='B';
g.vexs[2]='C';
g.vexs[3]='D';
g.vexs[4]='E';
int a[5][5]={{0,1,0,1,1},{ 1,0,1,0,1},{ 0,1,0,0,0},{ 1,0,0,0,0},{ 1,1,0,0,0}};
for(int i=0;i<5;i++)
for(int j=0;j<5;j++)
g.arcs[i][j]=a[i][j];
printf("圖按照鄰接矩陣儲存時的廣度優先遍歷:\n");
BFS_matrix(g,0,visited);
CreateAdjTable(ga,5);
printf("圖按照鄰接表儲存時的廣度優先遍歷:\n");
BFS_AdjTable(ga,0,visited1);
}
其結果如下圖:
從上面可以看出,兩種方式的結果不同,但都是正確的,因為這與鄰接點訪問的順序有關。
注:如果程式出錯,可能是使用的開發平臺版本不同,請點選如下連結: 解釋說明
作者:nineheadedbird