資料結構----BFS和DFS詳解
前言
The art of teaching is the art of assisting discovery.
Name:Willam
Time:2017/2/28
這篇部落格將會介紹兩種遍歷圖的演算法,一種是:DFS—-深度優先搜尋,另外一種就是:BFS–廣度優先搜尋。
1、DFS (深度優先搜尋)
演算法思路:
從頂點V開始,訪問這個頂點,然後依次從V的未被訪問的鄰接點出發深度優先遍歷圖,直至圖中所有和V有路徑的相通的頂點都被訪問了,如果此時還有頂點未被訪問,則選擇圖中未被訪問的那個頂點作為起點,重複上述動作。
具體的程式碼實現如下:
#include<iostream>
#include<string>
using namespace std;
//使用鄰接矩陣完成圖的遍歷
struct Graph_array {
int vexnum; //圖的頂點數
int edge; //圖的邊數
int ** arc; //鄰接矩陣
int kind; //0,為有向圖,1,為無向圖
string * infromation; //表示每個頂點的資訊
};
//使用鄰接矩陣表示的圖
void createGraph_by_array(int **edge,Graph_array & g) {
int i = 0 ;
g.arc = new int*[g.vexnum];//為鄰接矩陣開闢空間
for (i = 0; i < g.vexnum; i++)
{
g.arc[i] = new int[g.vexnum];
for (int j = 0; j < g.vexnum; j++)
g.arc[i][j] = 0;
}
for (i = 0; i < g.edge; i++)
{
//對矩陣進行賦值
g.arc[edge[i][0] - 1][edge[i][1 ] - 1] = 1;
}
}
//列印鄰接矩陣
void print_array(Graph_array g) {
int i = 0;
for (i = 0; i <g.vexnum; i++) {
//cout << g.infromation[i] << " ";
for (int j = 0; j < g.vexnum; j++) {
cout << g.arc[i][j] << " ";
}
cout << endl;
}
}
//進行DFS遍歷,
void DFS_store_array(Graph_array g,int v,bool * & visit) {
cout << g.infromation[v] << " ";
visit[v] = true;
for (int i = 0; i < g.vexnum; i++) {//找出下一個位被訪問的頂點
if (g.arc[v][i] == 0 || g.arc[v][i] == INT_MAX) { //如果兩個頂點不存在邊
continue;
}
else if (!visit[i]) {//如果沒有被訪問,訪問該結點
DFS_store_array(g, i, visit);
}
}
}
//呼叫對應的DFS函式進行圖的遍歷
void DFS_array_travel(Graph_array g,int begin) {
bool * visit;
visit = new bool[g.vexnum];
int i;
for (i = 0; i < g.vexnum; i++) {
visit[i] = false;
}
cout << "圖的DFS遍歷結果:" << endl;
DFS_store_array(g,begin - 1,visit);
//如果圖是非聯通同,那麼我們這裡還需要對每個頂點遍歷一次,保證
//全部頂點都被訪問了
for (i = 0; i < g.vexnum; i++) {
if (!visit[i])
DFS_store_array(g, i,visit);
}
}
//使用鄰接表表示圖進行圖的遍歷
//表結點
struct ArcNode {
int adfvex;//表示該邊的另外一個頂點在頂點表中的下標
ArcNode * next; //表示依附在該頂點的下一條邊的資訊
};
//頭結點
struct Vnode {
string data; //記錄基本資訊i
ArcNode * firstarc;//記錄第一條依附在該頂點的邊
};
//一個圖的結構
struct Graph_List {
int vexnum; //圖的頂點數
int edge; //圖的邊數
Vnode * node; //鄰接表
int kind; //0,為有向圖,1,為無向圖
};
//建立鄰接表
void createGraph_list(Graph_List & g, int **edge) {
int i;
for (i = 0; i < g.edge; i++)
{
ArcNode * next=new ArcNode;
next->adfvex=edge[i][1]-1;
next->next = NULL;
//判斷該頂點的是否已經有邊依附
if (g.node[edge[i][0]-1].firstarc == NULL) {
g.node[edge[i][0]-1].firstarc = next;
}
else {//尋找連結串列的最後一個結點
ArcNode * now;
now = g.node[edge[i][0]-1].firstarc;
while (now->next) {
now = now->next;
}
now->next = next;
}
}
}
//列印鄰接表
void print_list(Graph_List g) {
int i;
for (i = 0; i < g.vexnum; i++) {
cout << g.node[i].data << " ";
ArcNode * now;
now = g.node[i].firstarc;
while (now) {
cout << now->adfvex << " ";
now = now->next;
}
cout << endl;
}
}
//使用DFS進行遍歷圖,在鄰接表的情況下進行遍歷
void DFS_store_list(Graph_List g, int v, bool * & visit) {
cout << g.node[v].data << " ";
visit[v] = true;
ArcNode * next = g.node[v].firstarc;
while (next) {
if (!visit[next->adfvex]) {
DFS_store_list(g, next->adfvex, visit);//遞迴
}
else {
next = next->next;
}
}
}
//呼叫上面那個函式進行圖的遍歷
void DFS_list(Graph_List g, int begin) {
int i;
bool * visit = new bool[g.vexnum];
for (i = 0; i < g.vexnum; i++) {
visit[i] = false;
}
cout << "圖的DFS遍歷結果:" << endl;
DFS_store_list(g, begin - 1, visit);
for (i = 0; i < g.vexnum; i++) {
if(!visit[i])
DFS_store_list(g, i, visit);
}
}
int main()
{
Graph_array g;
Graph_List G;
int i;
cout << "輸入圖的種類:" << endl;
cin >> g.kind; G.kind = g.kind;
cout << "輸入圖的頂點個數" << endl;
cin >> g.vexnum; G.vexnum = g.vexnum;
cout << "輸入圖的邊的個數(輸入時注意,無向圖的邊要比看的邊乘以2,然後輸入的記得把重複的邊也要輸進去)" << endl;
cin >> g.edge; G.edge = g.edge;
g.infromation = new string[g.vexnum];
G.node = new Vnode[G.vexnum];
cout << "輸入每個頂點資訊(如名稱):" << endl;
for (i = 0; i < g.vexnum; i++) {
cin >> g.infromation[i];
G.node[i].data = g.infromation[i];
G.node[i].firstarc = NULL;
}
int ** edge_information;
edge_information = new int*[g.edge];
cout << "輸入每條邊兩個頂點的編號:" << endl;
for (i = 0; i < g.edge; i++)
{
edge_information[i] = new int[2];
cin >> edge_information[i][0];
cin >> edge_information[i][1];
}
int **arc; //鄰接矩陣
//構造鄰接矩陣,其中最後一次引數:1,代表無向圖,0,代表有向圖
createGraph_by_array(edge_information,g);
cout << "圖的鄰接矩陣為:" << endl;
print_array(g);
cout << endl;
DFS_array_travel(g, 1);
cout << endl;
createGraph_list(G, edge_information);
cout << "圖的鄰接表為:" << endl;
print_list(G);
cout << endl;
DFS_list(G, 1);
cout << endl;
system("pause");
return 0;
}
下面,我們對如下這個圖進行遍歷,
使用上述程式,輸出的結果為:
(輸入時注意,無向圖的邊要比看的邊乘以2,然後輸入的記得把重複的邊也要輸進去)
2、BFS (廣度優先搜尋)
BFS就是我們所說的廣度優先搜尋,它的思路就是:假設從圖中的頂點V出,在訪問了v之後,依次訪問v的各個未被訪問的鄰接點,然後,分別從這些鄰接點出發,依次訪問他們的鄰接點,並使“先被訪問的頂點的鄰接點”先於“後被訪問的鄰接點”先被訪問,直至圖中所有的頂點都被訪問到為止,防止出現非連通圖的情況,我們需要最後遍歷一遍,看是否所有的點都被訪問了,如果有未被訪問的點,那麼就把該點作為一個新的起點。
程式碼實現如下:
#include<iostream>
#include<string>
#include<queue>
using namespace std;
//使用鄰接矩陣完成圖的遍歷
struct Graph_array {
int vexnum; //圖的頂點數
int edge; //圖的邊數
int ** arc; //鄰接矩陣
int kind; //0,為有向圖,1,為無向圖
string * infromation; //表示每個頂點的資訊
};
//使用鄰接矩陣表示的圖
void createGraph_by_array(int **edge, Graph_array & g) {
int i = 0;
g.arc = new int*[g.vexnum];//為鄰接矩陣開闢空間
for (i = 0; i < g.vexnum; i++)
{
g.arc[i] = new int[g.vexnum];
for (int j = 0; j < g.vexnum; j++)
g.arc[i][j] = 0;
}
for (i = 0; i < g.edge; i++)
{
//對矩陣進行賦值
g.arc[edge[i][0] - 1][edge[i][1] - 1] = 1;
}
}
//列印鄰接矩陣
void print_array(Graph_array g) {
int i = 0;
for (i = 0; i <g.vexnum; i++) {
//cout << g.infromation[i] << " ";
for (int j = 0; j < g.vexnum; j++) {
cout << g.arc[i][j] << " ";
}
cout << endl;
}
}
//呼叫對應的BFS函式進行圖的遍歷
void BFS_array_travel(Graph_array g, int begin) {
bool * visit;
visit = new bool[g.vexnum];
int i;
for (i = 0; i < g.vexnum; i++) {
visit[i] = false;
}
cout << "圖的BFS遍歷結果:" << endl;
//通過我們之前說的演算法思路,我們可以知道
//我們需要使用先進先出的資料儲存結構實現我們的BFS,其實那就是佇列
queue<int> q;
for (int v = 0; v < g.vexnum; v++) {//這重迴圈是為了保證非連通同的情況下,每個頂點都可以被訪問
if (!visit[(begin-1 + v) % g.vexnum])//注意起點不一定是v1
{
cout << g.infromation[(begin - 1 + v) % g.vexnum] << " ";
visit[(begin - 1 + v) % g.vexnum] = true;
q.push((begin - 1 + v) % g.vexnum);//初始化我們的佇列
while (!q.empty())
{
int u = q.front();
q.pop();
for (int j = 0; j < g.vexnum; j++) {
if (g.arc[u][j] == 0 || g.arc[u][j] == INT_MAX) { //如果兩個頂點不存在邊
continue;
}
else if (!visit[j] ) {//先訪問所有和u相連的頂點,並且把它們加入佇列
cout << g.infromation[j] << " ";
visit[j] = true;
q.push(j);
}
}
}
}
}
cout << "完成" << endl;
}
//使用鄰接表表示圖進行圖的遍歷
//表結點
struct ArcNode {
int adfvex;//表示該邊的另外一個頂點在頂點表中的下標
ArcNode * next; //表示依附在該頂點的下一條邊的資訊
};
//頭結點
struct Vnode {
string data; //記錄基本資訊i
ArcNode * firstarc;//記錄第一條依附在該頂點的邊
};
//一個圖的結構
struct Graph_List {
int vexnum; //圖的頂點數
int edge; //圖的邊數
Vnode * node; //鄰接表
int kind; //0,為有向圖,1,為無向圖
};
//建立鄰接表
void createGraph_list(Graph_List & g, int **edge) {
int i;
for (i = 0; i < g.edge; i++)
{
ArcNode * next = new ArcNode;
next->adfvex = edge[i][1] - 1;
next->next = NULL;
//判斷該頂點的是否已經有邊依附
if (g.node[edge[i][0] - 1].firstarc == NULL) {
g.node[edge[i][0] - 1].firstarc = next;
}
else {//尋找連結串列的最後一個結點
ArcNode * now;
now = g.node[edge[i][0] - 1].firstarc;
while (now->next) {
now = now->next;
}
now->next = next;
}
}
}
//列印鄰接表
void print_list(Graph_List g) {
int i;
for (i = 0; i < g.vexnum; i++) {
cout << g.node[i].data << " ";
ArcNode * now;
now = g.node[i].firstarc;
while (now) {
cout << now->adfvex << " ";
now = now->next;
}
cout << endl;
}
}
//利用BFS進行圖的遍歷
void BFS_list(Graph_List g, int begin) {
int i;
bool * visit = new bool[g.vexnum];
for (i = 0; i < g.vexnum; i++) {
visit[i] = false;
}
cout << "圖的BFS遍歷結果:" << endl;
queue<int> q;
for (int v = 0; v < g.vexnum; v++) {
if (!visit[(begin - 1 + v) % g.vexnum])//注意起點不一定是v1
{
cout << g.node[(begin - 1 + v) % g.vexnum].data << " ";
visit[(begin - 1 + v) % g.vexnum] = true;
q.push((begin - 1 + v) % g.vexnum);//初始化我們的佇列
while (!q.empty())
{
int u = q.front();
q.pop();
ArcNode * next;
next = g.node[u].firstarc;//獲得依附在該頂點的第一條邊的資訊
while (next) {//遍歷該連結串列上的所有的點
if (!visit[next->adfvex]) {
cout << g.node[next->adfvex].data << " ";
visit[next->adfvex] = true;
q.push(next->adfvex);
}
next = next->next;
}
}
}
}
}
int main()
{
Graph_array g;
Graph_List G;
int i;
cout << "輸入圖的種類:" << endl;
cin >> g.kind; G.kind = g.kind;
cout << "輸入圖的頂點個數" << endl;
cin >> g.vexnum; G.vexnum = g.vexnum;
cout << "輸入圖的邊的個數" << endl;
cin >> g.edge; G.edge = g.edge;
g.infromation = new string[g.vexnum];
G.node = new Vnode[G.vexnum];
cout << "輸入每個頂點資訊(如名稱):" << endl;
for (i = 0; i < g.vexnum; i++) {
cin >> g.infromation[i];
G.node[i].data = g.infromation[i];
G.node[i].firstarc = NULL;
}
int ** edge_information;
edge_information = new int*[g.edge];
cout << "輸入每條邊兩個頂點的編號:" << endl;
for (i = 0; i < g.edge; i++)
{
edge_information[i] = new int[2];
cin >> edge_information[i][0];
cin >> edge_information[i][1];
}
int **arc; //鄰接矩陣
//構造鄰接矩陣,其中最後一次引數:1,代表無向圖,0,代表有向圖
createGraph_by_array(edge_information, g);
cout << "圖的鄰接矩陣為:" << endl;
print_array(g);
cout << endl;
BFS_array_travel(g, 1);
cout << endl;
createGraph_list(G, edge_information);
cout << "圖的鄰接表為:" << endl;
print_list(G);
cout << endl;
BFS_list(G, 1);
cout << endl;
system("pause");
return 0;
}
同樣是對DFS遍歷的那個圖進行遍歷,結果如下:
3、總結
上述我們採用兩種方式對圖進行的了遍歷包括了兩種表達方式:鄰接表和鄰接矩陣,首先我們看遍歷的方法上的選擇,其實這兩種遍歷演算法的時間複雜度是一樣的,它們的不同就是在於遍歷頂點的順序不同,另外,對於兩種圖的不同的表示方式,我們可以發現鄰接矩陣的表示下,圖遍歷的時間複雜度為:o(n*n),而在鄰接表的下,遍歷的時間複雜度只是:O(n+e),其中n為頂點個數,e為邊的條數,所以,在實際中需要進行圖的遍歷的情況下,我們最好是採用鄰接表的方式進行表示我們的圖。