1. 程式人生 > >資料結構:圖的遍歷--深度優先、廣度優先

資料結構:圖的遍歷--深度優先、廣度優先

                       圖的遍歷:深度優先、廣度優先

遍歷

    圖的遍歷是指從圖中的某一頂點出發,按照一定的策略訪問圖中的每一個頂點。當然,每個頂點有且只能被訪問一次。

    在圖的遍歷中,深度優先和廣度優先是最常使用的兩種遍歷方式。這兩種遍歷方式對無向圖和有向圖都是適用的,並且都是從指定的頂點開始遍歷的。先看下兩種遍歷方式的遍歷規則:

深度優先

    深度優先遍歷也叫深度優先搜尋(Depth First Search)。它的遍歷規則:不斷地沿著頂點的深度方向遍歷。頂點的深度方向是指它的鄰接點方向。

    具體點,給定一圖G=<V,E>,用visited[i]表示頂點i的訪問情況,則初始情況下所有的visited[i]都為false。假設從頂點V

0開始遍歷,則下一個遍歷的頂點是V0第一個鄰接點Vi,接著遍歷Vi第一個鄰接點Vj,……直到所有的頂點都被訪問過。

    所謂的第一個是指在某種儲存結構中(鄰接矩陣鄰接表),所有鄰接點中儲存位置最近的,通常指的是下標最小的。在遍歷的過程中有兩種情況經常出現

  1. 某個頂點的鄰接點都已被訪問過的情況,此時需回溯已訪問過的頂點。
  2. 圖不連通,所有的已訪問過的頂點都已回溯完了,仍找不出未被訪問的頂點。此時需從下標0開始檢測visited[i],找到未被訪問的頂點i,從i開始新一輪的深度搜索。
看一個例子
從V0開始遍歷     遍歷分析:V0有兩個鄰接點V1V2,選擇下標最小的V1遍歷。接著從V1開始深度遍歷,
V1只有鄰接點V3,也就是沒有選的:遍歷V3。接著從V3開始遍歷,V3只有鄰接點V0,而V0已經被遍歷過。此時出現了上面提到的情況一,開始回溯V1V1無未被遍歷的鄰接點,接著回溯V0V0有一個未被遍歷的鄰接點V2,新的一輪深度遍歷從V2開始。V2無鄰接點,且無法回溯。此時出現了情況二,檢測visited[i],只有V4了。深度遍歷完成。看到回溯,應該可以想到需要使用棧。
遍歷序列是 V0->V1->V3->V2->V4 從其它頂點出發的深度優先遍歷序列是: V1->V3->V0->V2->V4 V2->V0->V1->V3->V
4

V3->V0->V1->V2->V4
V4->V2->V0->V1->V3
以上結果,我們稍後用於測試程式。 結合在圖的實現:鄰接矩陣中的程式碼,我們看下在鄰接矩陣形式下的圖的深度遍歷演算法:

深度優先程式碼

/*
深度優先搜尋
從vertex開始遍歷,visit是遍歷頂點的函式指標
*/
void Graph::dfs(int vertex, void (*visit)(int))
{
	stack<int> s;
	//visited[i]用於標記頂點i是否被訪問過
	bool *visited = new bool[numV];
	//count用於統計已遍歷過的頂點數
	int i, count;
	for (i = 0; i < numV; i++)
		visited[i] = false;
	count = 0;
	while (count < numV)
	{
		visit(vertex);
		visited[vertex] = true;
		s.push(vertex);
		count++;
		if (count == numV)
			break;
		while (visited[vertex])
		{
			for (i = 0; i < numV
				&& (visited[i] 
				|| matrix[vertex][i] == 0 || matrix[vertex][i] == MAXWEIGHT); i++);
			if (i == numV)  //當前頂點vertex的所有鄰接點都已訪問完了
			{
				if (!s.empty())
				{
					s.pop();   //此時vertex正是棧頂,應先出棧
					if (!s.empty())
					{
						vertex = s.top();
						s.pop();
					}
					else  //若棧已空,則需從頭開始尋找新的、未訪問過的頂點
					{
						for (vertex = 0; vertex < numV && visited[vertex]; vertex++);
					}
				}
				else  //若棧已空,則需從頭開始尋找新的、未訪問過的頂點
				{
					for (vertex = 0; vertex < numV && visited[vertex]; vertex++);
				}
			}
			else  //找到新的頂點應更新當前訪問的頂點vertex
				vertex = i;
		}
	}
	delete[]visited;
}
其它程式碼前面已經見過,就不給出了,下面看下圖的廣度遍歷。深度遍歷和廣度遍歷的測試,稍後一併給出。

廣度優先

    廣度優先遍歷也叫廣度優先搜尋(Breadth First Search)。它的遍歷規則:
  1. 先訪問完當前頂點的所有鄰接點。(應該看得出廣度的意思)
  2. 先訪問頂點的鄰接點先於後訪問頂點的鄰接點被訪問。
    具體點,給定一圖G=<V,E>,用visited[i]表示頂點i的訪問情況,則初始情況下所有的visited[i]都為false。假設從頂點V0開始遍歷,且頂點V0的鄰接點下表從小到大有ViVj...Vk。按規則1,接著應遍歷ViVjVk。再按規則2,接下來應遍歷Vi的所有鄰接點,之後是Vj的所有鄰接點,...,最後是Vk的所有鄰接點。接下來就是遞迴的過程... 在廣度遍歷的過程中,會出現圖不連通的情況,此時也需按上述情況二來進行:測試visited[i]...。在上述過程中,可以看出需要用到佇列。 舉個例子,還是同樣一幅圖:
從V0開始遍歷     遍歷分析:V0有兩個鄰接點V1V2,於是按序遍歷V1V2V1先於V2被訪問,於是V1的鄰接點應先於V2的鄰接點被訪問,那就是接著訪問V3V2無鄰接點,只能看V3的鄰接點了,而V0已被訪問過了。此時需檢測visited[i],只有V4了。廣度遍歷完畢。
遍歷序列是
V0->V1->V2->V3->V4 從其它頂點出發的廣度優先遍歷序列是 V1->V3->V0->V2->V4 V2->V0->V1->V3->V4
V3->V0->V1->V2->V4
V4->V2->V0->V1->V3
以上結果,我們同樣用於測試程式。
在鄰接矩陣下,圖的廣度遍歷演算法

廣度優先程式碼

/*
廣度優先搜尋
從vertex開始遍歷,visit是遍歷頂點的函式指標
*/
void Graph::bfs(int vertex, void(*visit)(int))
{
	//使用佇列
	queue<int> q;
	//visited[i]用於標記頂點i是否被訪問過
	bool *visited = new bool[numV];
	//count用於統計已遍歷過的頂點數
	int i, count;
	for (i = 0; i < numV; i++)
		visited[i] = false;
	q.push(vertex);
	visit(vertex);
	visited[vertex] = true;
	count = 1;
	while (count < numV)
	{
		if (!q.empty())
		{
			vertex = q.front();
			q.pop();
		}
		else
		{
			for (vertex = 0; vertex < numV && visited[vertex]; vertex++);
			visit(vertex);
			visited[vertex] = true;
			count++;
			if (count == numV)
				return;
			q.push(vertex);
		}
		//程式碼走到這裡,vertex是已經訪問過的頂點
		for (int i = 0; i < numV; i++)
		{
			if (!visited[i] && matrix[vertex][i] > 0 && matrix[vertex][i] < MAXWEIGHT)
			{
				visit(i);
				visited[i] = true;
				count ++;
				if (count == numV)
					return;
				q.push(i);
			}
		}
	}
	delete[]visited;
}
結合兩種遍歷的程式碼,我們對同一幅圖進行測試,它的主函式是
void visit(int vertex)
{
	cout << setw(4) << vertex;
}
int main()
{
	cout << "******圖的遍歷:深度優先、廣度優先***by David***" << endl;
	bool isDirected, isWeighted;
	int numV;
	cout << "建圖" << endl;
	cout << "輸入頂點數 ";
	cin >> numV;
	cout << "邊是否帶權值,0(不帶) or 1(帶) ";
	cin >> isWeighted;
	cout << "是否是有向圖,0(無向) or 1(有向) ";
	cin >> isDirected;
	Graph graph(numV, isWeighted, isDirected);
	cout << "這是一個";
	isDirected ? cout << "有向、" : cout << "無向、";
	isWeighted ? cout << "有權圖" << endl : cout << "無權圖" << endl;
	graph.createGraph();
	cout << "列印鄰接矩陣" << endl;
	graph.printAdjacentMatrix();
	cout << endl;
	cout << "深度遍歷" << endl;
	for (int i = 0; i < numV; i++)
	{
		graph.dfs(i, visit);
		cout << endl;
	}
	cout << endl;
	cout << "廣度遍歷" << endl;
	for (int i = 0; i < numV; i++)
	{
		graph.bfs(i, visit);
		cout << endl;
	}
	system("pause");
	return 0;
}
執行


仔細對照測試結果,我們的程式碼是沒有問題的。

小結

對於某個圖來說,深度優先遍歷和廣度優先遍歷的序列不是唯一的,但當圖的儲存結構一確定,它的遍歷序列就是唯一的。因為當有多個候選點時,我們總是優先選擇下標最小的。
若有所幫助,頂一個哦!

專欄目錄: