圖的深度優先搜索(Depth First Search)

算法思想:

訪問給定的起始點v,從v出發,訪問它的一個不曾被訪問過的鄰接頂點w1;再從w1出發,訪問w1的一個不曾被訪問過的鄰接頂點w2;…,如此下去,直至到達了一個頂點,它沒有未訪問的鄰接頂點爲止。退回一步到上一個被訪問的頂點,看它是否還有未被訪問的鄰接頂點。若有,則訪問該鄰接頂點,且從它出發,進行前述的類似訪問。若沒有,則再退回一步進行搜索。當所有頂點均被訪問,則過程終止。

注意我們下面給出的實現,都是基於前面講解的圖的鄰接表的實現。由於前面給出的鄰接表實現是基於有向權圖,而我們現在要講解的圖的遍歷幾乎與權值無關,因此我們可以修改前面的代碼作爲非權圖使用。但是此處我們仍然採用前面的實現,邊的權值可以設爲0、1、或其他任意值(這裏我們給出了權值,但是我們不用它)。

一、遞歸實現

1、作爲非成員函數

注意前面給出的Graph爲一個模板類,所以該函數是模版函數

#include <cstdlib>
#include <iostream>
#include "graph.h"

/*從序號爲v的頂點出發,深度優先遍歷圖 
 *遞歸實現1
*/
template<class T>
void DFS(const Graph<T> &g, const int v, vector<bool>& visit)
{
	cout<<g.getVertexName(v)<<" ";//輸出序號爲v的頂點的名稱 
	visit[v] = true;			//標記頂點v已被訪問 
	
	int w = g.getFirstNeighbor(v);//v的第一個鄰接頂點 
	while(w != -1)				//若存在鄰接頂點 
	{
		if(!visit[w])			//鄰接頂點w未被訪問過 
		{
			DFS(g, w, visit);
		}
		w = g.getNextNeighbor(v,w);//獲得v相對w的下一個鄰接頂點 
	}
}


int main(int argc, char *argv[])
{
	Graph<char> graph("graph2.dat");
	
	cout<<"after read the graph :"<<endl;
	graph.printGraph();
	
	int n = graph.getNumberOfVertex();
	vector<bool> visit(n,false);
	cout<<"DFS: ";
	DFS(graph,0,visit);
	cout<<endl<<endl;
	
    system("PAUSE");
    return EXIT_SUCCESS;
}

測試結果:

(1)使用測試文件graph2.dat輸出爲:

DFS: A G F B C E D

(2)使用測試文件graph3.dat輸出爲:

DFS: A C B

我們發現,對於連通圖,該算法是正確的,但是對於非連通圖,上面的算法是錯誤的,它不能完整地遍歷圖的各個頂點。正確的算法如下所示:

#include <cstdlib>
#include <iostream>
#include "graph.h"


/*從序號爲v的頂點出發,深度優先遍歷圖 
 *遞歸實現1 
*/
template<class T>
void DFS(const Graph<T> &g, const int v, vector<bool>& visit)
{
	cout<<g.getVertexName(v)<<" ";//輸出序號爲v的頂點的名稱 
	visit[v] = true;			//標記頂點v已被訪問 
	
	int w = g.getFirstNeighbor(v);//v的第一個鄰接頂點 
	while(w != -1)				//若存在鄰接頂點 
	{
		if(!visit[w])			//鄰接頂點w未被訪問過 
		{
			DFS(g, w, visit);
		}
		w = g.getNextNeighbor(v,w);//獲得v相對w的下一個鄰接頂點 
	}
}

template<class T>
void depthFirstSearch(const Graph<T> &g, vector<bool>& visit)
{
	int n = g.getNumberOfVertex();
	for(int v=0; v<n; v++)
	{
		if(!visit[v])
		{
			DFS(g,v,visit);
		}
	}
}

int main(int argc, char *argv[])
{
	Graph<char> graph("graph3.dat");
	
	cout<<"after read the graph :"<<endl;
	graph.printGraph();
	
	int n = graph.getNumberOfVertex();
	vector<bool> visit(n,false);
	cout<<"DFS: ";
//	DFS(graph,0,visit);
	depthFirstSearch(graph,visit);
	cout<<endl<<endl;
	
    system("PAUSE");
    return EXIT_SUCCESS;
}


2、作爲成員函數

有了上面的講解,這個就比較簡單了

在private部分添加如下函數聲明:

void depthFirstSearch(const int v, bool* visit)const;

在public部分添加如下函數聲明:

void depthFirstSearch()const;
實現如下:
//private深度優先遍歷遞歸函數的實現 
template<class T>
void Graph<T>:: depthFirstSearch(const int v, bool* visit)const
{
	cout<<getVertexName(v)<<" ";//輸出序號爲v的頂點的名稱 
	
	visit[v] = true;			//標記頂點v已被訪問 
	
	int w = getFirstNeighbor(v);//v的第一個鄰接頂點 
	while(w != -1)				//若存在鄰接頂點 
	{
		if(!visit[w])			//鄰接頂點w未被訪問過 
		{
			depthFirstSearch(w, visit);
		}
		w = getNextNeighbor(v,w);//獲得v相對w的下一個鄰接頂點 
	}
} 

//public深度優先遍歷實現 
template<class T>
void Graph<T>:: depthFirstSearch()const
{
	int n = getNumberOfVertex();
	bool * visit = new bool[n];
	
	for(int i=0; i<n; i++)
	{
		visit[i] = false;
	}
	for(int v=0; v<n; v++)
	{
		if(!visit[v])
		{
			depthFirstSearch(v,visit);
		}
	}
	delete[] visit;
}

二、迭代實現

基本思想:

(1)檢測堆棧是否爲空。若堆棧爲空,則迭代結束;否則,從棧中彈出一個頂點v;

(2) 訪問v,將visit[v]值更新爲true;

(3) 求出v的鄰接頂點表,將v的未被訪問的鄰接頂點壓入棧,執行步驟(1)。

由於不想每次更改Graph.h頭文件,所以下面就直接給出非成員函數的實現,有興趣自己可以參照上面給出成員函數實現。

#include <cstdlib>
#include <iostream>
#include <stack>
#include "graph.h"

template<class T>
void iterativeDFS(const Graph<T>& g, const int v, vector<bool>& visit)
{
	stack<int> s;
	s.push(v);
	
	int w,k;
	while(!s.empty())
	{
		w = s.top();
		s.pop();
		if(!visit[w])//頂點w未被訪問
		{
			visit[w] = true;
			cout<<g.getVertexName(w)<<" ";
			k = g.getFirstNeighbor(w);
			while(k != -1)
			{
				if(!visit[k])
				{
					s.push(k);// 若k未被訪問過,將k壓入棧
				}
				k = g.getNextNeighbor(w,k);
			}
		} 
	}
}

template<class T>
void iDepthFirstSearch(const Graph<T>& g, vector<bool>& visit)
{
	int n = g.getNumberOfVertex();
	for(int v=0; v<n; v++)
	{
		if(!visit[v])
			iterativeDFS(g,v,visit);
	}
}

int main(int argc, char *argv[])
{
	Graph<char> graph("graph3.dat");
	
	cout<<"after read the graph :"<<endl;
	graph.printGraph();
	
	int n = graph.getNumberOfVertex();
	vector<bool> visit(n,false);
	cout<<"IDFS: ";
	iDepthFirstSearch(graph,visit);
	cout<<endl<<endl;
	
    system("PAUSE");
    return EXIT_SUCCESS;
}

三、測試文件

1、測試文件graph2.dat

7

AB C D E F G

A B 1

A G 1

B C 1

C D 1

C E 1

D E 1

E F 1

F A 1

G F 1

2、測試文件graph3.dat

5

A B C D E

A B 1

A C 1

D E 1

注意:

深度優先遍歷序列不是唯一的,它與鄰接表的實際存儲內容有關,也與採用的算法是遞歸還是迭代有關。

參考資料:

[1]《數據結構》劉大有 唐海鷹等著 高等教育出版社

[2][嚴蔚敏《數據結構(C語言版)》




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章