資料結構——圖的廣度優先遍歷
圖的廣度遍歷和深度遍歷思想不一樣。後者是用遞迴的方法來實現的,這個是要藉助佇列來實現的。
實現的基本思想如下:
1、從圖中某個頂點V0出發,並訪問此頂點;
2、從V0出發,訪問V0的各個未曾訪問的鄰接點W1,W2,…,Wk;然後,依次從W1,W2,…,Wk出發訪問各自未被訪問的鄰接點;
3、重複步驟2,直到全部頂點都被訪問為止。</br>
廣度優先遍歷是以層為順序,和樹的層次遍歷差不多,是將某一層上的所有節點都搜尋到了之後才向下一層搜尋;而深度優先遍歷是將某一條枝椏上的所有節點都搜尋到了之後,才轉向搜尋另一條枝椏上的所有節點。
深度優先遍歷從某個頂點出發,首先訪問這個頂點,然後找出剛訪問這個結點的第一個未被訪問的鄰結點,然後再以此鄰結點為頂點,繼續找它的下一個新的頂點進行訪問,重複此步驟,直到所有結點都被訪問完為止。
廣度優先遍歷從某個頂點出發,首先訪問這個頂點,然後找出這個結點的所有未被訪問的鄰接點,訪問完後再訪問這些結點中第一個鄰接點的所有結點,重複此方法,直到所有結點都被訪問完為止。
可以看到兩種方法最大的區別在於前者從頂點的第一個鄰接點一直訪問下去再訪問頂點的第二個鄰接點;後者從頂點開始訪問該頂點的所有鄰接點再依次向下,一層一層的訪問。
在這裡我是用對圖的儲存方式是用鄰接矩陣來實現的。
這種方法是用一個一維陣列來表示頂點,用一個二緯陣列來實現對邊的儲存,若a和b之間有邊,那就讓二位陣列中的這兩個資料對應的資料中填入1;沒有迴路用一個很大的樹來表示就好了
具體可參考 ofollow,noindex">圖的鄰接矩陣百度百科
對於圖的廣度遍歷的具體的演示過程請看 廣度優先優酷
在這裡給出C++的程式碼
在這裡寫的是無向圖,測試用的是無向圖,其實有向圖和這個基本一樣,只是加了一個邊的方向,想要修改也就是在搜尋下一條邊的時候要搜兩次,一次是a到b,一個是b到a;
這是佇列的實現程式碼
// //Graph.hpp //圖的廣度優先 // //Created by 橘子和香蕉 on 2018/11/26. //Copyright © 2018 橘子和香蕉. All rights reserved. // /* 這這裡用連結串列的形式來實現佇列; 佇列的實現有兩種,一種是陣列,迴圈佇列。一種是連結串列 兩個指標,一個指向頭,一個指向尾。資料是在尾指標新增,在頭指標刪除。 */ #include <iostream> using namespace std; typedef struct qnode{ int position; qnode *next; }qnode; class Cqueue{ private: qnode *front;//頭指標 qnode *rear;//尾指標 int length;//佇列的長度 public: Cqueue(); ~Cqueue(); void inQueue(int data);//data入隊 intoutQueue();//出佇列 void printCqueue();//輸出佇列 bool isEmpty();//判斷佇列是不是空 }; Cqueue::Cqueue(){ front =new qnode; front->next = NULL; rear = front; length = 0; } Cqueue::~Cqueue(){ qnode *p; while (front != NULL ) { p = front; front = front->next; delete p; } } void Cqueue::inQueue(int data){ //qnode *n = new qnode; //n->position = data; //n->next = rear->next; //rear->next = n; //rear = n; //if(front ->next == NULL ){ //front->next = n; //} //length ++; qnode *p = new qnode; p->position = data; p->next = NULL; rear->next = p; rear = p; length++; } int Cqueue::outQueue(){ if(front == rear){ cout<<"error\n"; return -1; } //qnode *p = front ->next; //int data = p->position; //front->next = p->next; //if(p->next == NULL){ //rear = front; //} //delete p; //length--; //return data; qnode *p = front->next; int data = p->position; front->next = p->next; if(p->next ==NULL){ rear = front; } delete p; length--; return data; } void Cqueue::printCqueue(){ qnode *p = front->next; while (p != NULL) { cout<<p->position<<"\t"; p = p->next; } cout<<"length"<<length<<endl; cout<<endl; } boolCqueue::isEmpty(){ return front == rear?true:false; }
下面是圖的實現程式碼:
// //Graph.hpp //圖的廣度優先 // //Created by 橘子和香蕉 on 2018/11/26. //Copyright © 2018 橘子和香蕉. All rights reserved. // #include <iostream> using namespace std; #define VERTEXNUM 100 #define dataType char typedef struct node{ dataType data; bool isAccess; }node; class Graph{ private: node vertex[VERTEXNUM];//頂點表 int edge[VERTEXNUM][VERTEXNUM];//邊表 int vertexNum;//頂點個數 int edgeNum;//邊的個數 int locate(dataType data);//在頂點表中找data的位置 bool isHaveNevEdge(dataType data);//data是不是還有鄰接點 void move(dataType data);//若一個頂點被刪除後,沒有鄰接點,那就從頂點表中刪除這個元素,然後在頂點表和邊表中都要移動資料元素。 intgetFirstVertex(dataType data);//得到元素的第一個鄰接點位置。 intgetNextNevVertex(dataType data,dataTypeFrontData);//得到data頂點從Frontdata之後的第一個頂點位置。 public: void init(int vertex ,int edgeNum);//初始化邊數和頂點數。並且初始化邊表的陣列。 voidcreate();//建立圖 void printGraph();//輸出 void addEdge(dataType start,dataType end);//新增一條邊 void deleteEdege(dataType start,dataType end);//刪除一條邊 void breadthFirstSearch(dataType data);//廣度優先遍歷 }; int Graph::locate(dataType data){ for (int i= 0; i<vertexNum;i++) { if(vertex[i].data == data){ return I; } } return -1; } void Graph::create(){ cout<<"input Graph data\n"; for (int i = 0; i<vertexNum; i++) { cin>>vertex[i].data; vertex[i].isAccess= false; } for (int j = 0; j<edgeNum; j++) { cout<<"input start and end of edge:\n"; char start ,end; cin>>start>>end; int startPosition = locate(start); int endPosition = locate(end); edge[startPosition][endPosition] = 1; edge[endPosition][startPosition] = 1; } } void Graph::init(int vertex, int edgeNum){ this->vertexNum = vertex; this->edgeNum = edgeNum; for (int i = 0; i<VERTEXNUM; i++) { for (int j = 0; j<VERTEXNUM; j++) { edge[i][j] = INT_MAX; } } } void Graph::printGraph(){ cout<<endl; cout<<endl; cout<<"頂點邊:\n"; cout<<"vertexNum:"<<vertexNum<<" edgeNum:"<<edgeNum<<endl; for (int i = 0; i<vertexNum; i++) { cout<<vertex[i].data<<"\t"; } cout<<"邊表如下:\n"; for (int j = 0; j<edgeNum; j++) { for (int k = 0; k<edgeNum ; k++) { cout<<edge[j][k]<<"\t"; } cout<<endl; } } boolGraph::isHaveNevEdge( dataType data ){ int position = locate(data); for (int i = 0; i<vertexNum; i++) { if(edge[position][i] !=INT_MAX){ return true; } } return false; } void Graph::move(dataType data){ int positon = locate(data); for (int i = positon; i<vertexNum; i++) { vertex[i].data = vertex[i+1].data; vertex[i].isAccess = vertex[i+1].isAccess; } vertexNum --; for (int i = positon; i<vertexNum; i++) { for (int j = 0; j<vertexNum; j++) { edge[i][j] = edge[i+1][j]; } } edgeNum--; } void Graph::addEdge(char start, char end){ int startPositon = locate(start); int endPosition = locate(end); if(startPositon == -1 && endPosition != -1){ vertex[vertexNum].data = start; vertex[vertexNum].isAccess = false; this->vertexNum+=1; this->edgeNum+=1; startPositon = vertexNum-1; } if(startPositon != -1 && endPosition == -1){ vertex[vertexNum].data = end; vertex[vertexNum].isAccess = false; this->vertexNum+=1; this->edgeNum+=1; endPosition = vertexNum-1; } if(startPositon == -1 && endPosition == -1){ vertex[vertexNum].data = start; vertex[vertexNum].isAccess = false; this->vertexNum+=1; this->edgeNum+=0; startPositon = vertexNum-1; vertex[vertexNum].data = end; vertex[vertexNum].isAccess = false; this->vertexNum+=1; this->edgeNum+=1; endPosition = vertexNum-1; } edge[startPositon][endPosition] = 1; edge[endPosition][startPositon] = 1; cout<<startPositon<<endPosition; cout<<edge[startPositon][endPosition]<<";"<<edge[endPosition][startPositon] <<endl; } void Graph::deleteEdege(dataType start, dataType end){ int startPosition = locate(start); int endPosition = locate(end); if(startPosition == -1 || endPosition == -1){ cout<<"error\n"; return; } edge[startPosition][endPosition] = INT_MAX; edge[endPosition][startPosition] = INT_MAX; if(! isHaveNevEdge(start)){ move(start); } if(! isHaveNevEdge(end)){ move(end); } } intGraph::getFirstVertex(dataType data){ int position = locate(data); for (int i = 0; i<vertexNum; i++) { if( edge[position][i] == 1 ){ return I; } } return -1; } int Graph::getNextNevVertex(dataType data ,dataType frontData){ int position = locate(data); int frontPosition = locate(frontData); for (int i = frontPosition+1 ; i<vertexNum; i++) { if(edge[position][i] == 1){ return I; } } return-1; } void Graph::breadthFirstSearch(dataType data){ cout<<"DFS:\n"; Cqueue queue; int position = locate(data); int queueHeadPosition = -1; if(position == -1){ cout<<"the vettex is not exist\n"; return; } vertex[position].isAccess = true; queue.inQueue(position);//入佇列 cout<<position<<endl; while(queue.isEmpty() == false){ queueHeadPosition = queue.outQueue();//獲得佇列的頭元素 cout<<vertex[queueHeadPosition].data<<"\t"; for ( int i = getFirstVertex(vertex[queueHeadPosition].data);//獲得佇列頭元素的第一個鄰接點 i>=0; i = getNextNevVertex(vertex[queueHeadPosition].data, vertex[i].data)//獲得從i之後下一個鄰接點 ) { if(vertex[i].isAccess == false){ queue.inQueue(i); vertex[i].isAccess = true; //cout<<vertex[i].data<<"\t"; } } } cout<<endl; }
下面給出main函式:
// //main.cpp //圖的廣度優先 // //Created by 橘子和香蕉 on 2018/11/26. //Copyright © 2018 橘子和香蕉. All rights reserved. // #include <iostream> using namespace std; int main(){ //Cqueue a; //a.inQueue(3); //a.inQueue(4); //a.inQueue(5); //a.printCqueue(); //a.outQueue(); //a.printCqueue(); //a.outQueue(); //a.printCqueue(); //for (int i = 23; i<40; i++) { //a.inQueue(i); //} //a.printCqueue(); Graph s; s.init(4, 4); s.create(); s.printGraph(); //s.addEdge('b', 'e'); //s.printGraph(); //s.deleteEdege('b', 'e'); //s.printGraph(); //s.breadthFirstSearch('d'); //s.breadthFirstSearch('b'); s.breadthFirstSearch('a'); return 1; }
注意:在測試的時候不能在一次執行的時候連續從一個頂點開始廣度優先遍歷,因為在第一次遍歷的時候就將其設定為訪問過,第二次遍歷的時候就不能繼續訪問了,這個問題也是很好解決的,就是在新增一個函式用來沒次訪問完了後將所有的結點設定為沒有訪問過就好了,這樣就可以在一次執行中連續執行。而不是下面這樣,需要註釋幾個,每次只能從一個結點出發。

image.png