對無向圖的深度優先搜索(DFS)


【0】README

0.1) 本文總結於 數據結構與算法分析, 源代碼均為原創, 旨在 理解 如何對無向圖進行深度優先搜索 的idea 並用源代碼加以實現;
0.2) 本文還引入了 背向邊(定義見下文描述),並用源代碼找出了給定圖的在 DFS過程中 產生的背向邊, 但是要注意 背向邊不是深度優先搜索樹的邊, 該樹是由 對給定圖進行DFS生成的;
0.3) 通過打印 parent (可以看做是 深度優先搜索樹的邊), 我們可以大致知曉 深度優先搜索樹的大致框架,並結合背向邊,我們就可以將 DFS 的全部流程看清楚了;
0.4)背向邊的添加操作是比較重要的: 因為后面的DFS應用到 ”雙連通性“、”有向圖“以及”查找強分支“ 都要用到 背向邊;


【1】DFS應用與無向圖相關

1.1)DFS應用與測試無向圖是否連通:無向圖是連通的, 當且僅當從任一節點開始的深度優先搜索訪問到每一個節點。因為這項測試應用起來非常容易, 所以我們將假設我們處理的圖都是連通的。如果它們不連通,那么我們可以找出所有的連通分支並將我們的算法依次應用於每個分支;
1.2)看個荔枝:
這里寫圖片描述
對上圖的深度優先搜索步驟(steps)進行分析(Analysis)(這只是深度優先搜索的一種可能情況)

  • A1)建立深度優先生成樹的步驟

    • step1)任取一個 頂點A;
    • step2)將 頂點A 標記為已訪問過, 並遞歸調用 DFS(B);
    • step3)將 頂點B 標記為已訪問過, 並遞歸調用 DFS(C);
    • step4)將 頂點C 標記為已訪問過, 並遞歸調用 DFS(D);
    • step5)將 頂點D 標記為已訪問過, 由於 頂點A,頂點C 均被訪問過,所以return;
    • step6)遞歸return到 頂點C, 並遞歸調用DFS(E);
    • step7)將 頂點E 標記為已訪問過, 由於沒有未被訪問過的頂點,所以算法over;
  • A2)對邊的處理(引入背向邊的定義): 如果當我們處理(v, w)時 發現w是未被標記的, 或當我們處理(w, v)時發現v是未標記的,那么我們就用樹的一條邊表示它; 如果當我們處理 (v, w)時發現w 是已被標記的, 並且當我們處理(w, v)時發現v 也是已標記的, 那么我們就畫一條虛線, 並稱之為背向邊,表示這條邊實際上不是樹的一部分;

1.3)樹將模擬我們執行的遍歷順序, 如右上圖所示。只使用樹的邊對該樹的先序編號告訴我們這些頂點被標記的順序。 如果圖不是連通的, 那么處理所有節點(和邊)都需要多次調用DFS, 每次都生成一顆樹,整個集合就是 深度優先生成森林(depth-first spanning forest)如右上圖所示;
1.4)深度優先搜索無向圖的遞歸步驟解析如下:

1.5)看個荔枝——對給定圖在DFS過程中添加背向邊(backside)的過程
這里寫圖片描述


【2】source code + printing results

2.0)code specification:
我個人以為代碼中的干貨,一個當然是 DFS 的遞歸過程,另一個則是 添加背向邊的代碼,它的if 條件語句如下:

if(visited[adjVertex]) // judge whether the adjVertes was visited before		
		{
			if(vertexIndex[vertex] > vertexIndex[adjVertex] && parent[vertex] != adjVertex) 	
			{
				parent[adjVertex] = vertex; // building back side, attention of condition of building back side above
				
				// just for printing effect
				for(i = 0; i < depth; i++)  
					printf("           ");
				printf("vertex[%c]->vertex[%c] (backside) \n", flag[vertex], flag[adjVertex]);
			}
		}
  • s1)對以上代碼的分析: 當頂點vertex 的鄰接頂點被訪問過時,如果該鄰接頂點 晚於 當前頂點vertex 訪問, 且 該鄰接頂點不是當前頂點vertex的 parent的話,那么就符合添加 背向邊的條件了;(這里有點打腦筋,慢慢理解)
  • s2)我們講為什么要添加背向邊? 因為我們的初衷是要讓 深度優先搜索樹的各個頂點的可達程度 和 給定的初始圖的頂點可達情況類似或相同;你要想, 如果存在 (v1,v2) ,那必然有(v2,v1), 所以如果v1 是v2的parent,此時如果你又添加了 v2到 v1的背向邊,那豈不是沒有意義;我們再想, 如果 v1先於v2被訪問, 你又添加了 v1到v2的背向邊,那豈不是還是沒有意義,請對照到上述代碼中的 if 條件語句以便理解 為什么那樣去添加背向邊

2.1)download source code: https://github.com/pacosonTang/dataStructure-algorithmAnalysis/tree/master/chapter9/p241_dfs_undirected_graph
2.2)source code at a glance:(for complete code , please click the given link above)

#include "dfs.h"

extern char flag[];

void dfs(Vertex vertex, int depth)
{
	int i;
	AdjTable temp;
	Vertex adjVertex;	
	
	//printf("\n\t visited[%c] = 1 ", flag[vertex]);
	visited[vertex] = 1; // update visited status of vertex
	vertexIndex[vertex] = counter++; // number the vertex with counter
	temp = adj[vertex];	

	while(temp->next)
	{
		adjVertex = temp->next->vertex;		
		if(visited[adjVertex]) // judge whether the adjVertes was visited before		
		{
			if(vertexIndex[vertex] > vertexIndex[adjVertex] && parent[vertex] != adjVertex) 	
			{
				parent[adjVertex] = vertex; // building back side, attention of condition of building back side above
				
				// just for printing effect
				for(i = 0; i < depth; i++)  
					printf("           ");
				printf("vertex[%c]->vertex[%c] (backside) \n", flag[vertex], flag[adjVertex]);
			}
		}
		
		else
		{
			parent[adjVertex] = vertex;
			
			// just for printing effect
			for(i = 0; i < depth; i++)  
				printf("           ");
			printf("vertex[%c]->vertex[%c] (building edge)\n", flag[vertex], flag[adjVertex]);			
			dfs(adjVertex, depth+1);
		}
		
		temp = temp->next;
	} 
} 
 

2.3)printing results:
這里寫圖片描述

posted @ 2015-11-22 20:14  PacosonSWJTU  閱讀( 4001)  評論( 0編輯  收藏


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM