1. 程式人生 > >三、【圖演算法】廣度優先搜尋(BFS)

三、【圖演算法】廣度優先搜尋(BFS)

圖演算法是個龐大的家族,其中大部分成員的主體框架都可以歸結於圖的遍歷。圖的遍歷需要訪問所有頂點一次且僅 一次,此外,圖遍歷同時還要訪問所有的邊一次且僅一次。經過一次遍歷,樹邊的頂點共同構成了原圖的一顆遍歷樹

為防止對頂點的重複訪問,在遍歷的過程中,需要動態地設定各頂點的不同狀態,並且隨著遍歷的程序不斷地轉換狀態,直至最後的“訪問完畢”,圖的遍歷更加強調對處於特定狀態頂頂啊的甄別和查詢,故也稱作圖搜尋

最基本而典型的圖搜尋演算法包括:廣度優先搜尋(BFS),深度優先搜尋(DFS),優先順序搜尋等(PFS),本文主要介紹圖的廣度優先搜尋(breadth-first search,BFS)

,本文使用的圖資料結構參見之前部落格https://blog.csdn.net/qq_18108083/article/details/84870399

策略:越早被訪問到的頂點,其鄰居頂點越優先被選用。始自圖中頂點s的BFS搜尋,將首先訪問頂點s,再依次訪問頂點s所有未成訪問過的鄰居頂點,再按照後者被訪問的先後次序,組個訪問它們的鄰居。這個過程很類似與樹的層次遍歷,具有先入先出的特點,故採用佇列結構作為輔助容器。

實現:由於整個圖可能具有多個連通域,從單個頂點開始的BFS可能不能遍歷到圖中的所有頂點,所以BFS函式能夠遍歷從頂點s開始的單個連通域,而bfs函式則對所有頂點進行檢查,只要未曾被訪問過,就從該點開始一次新的BFS搜尋,這樣就能保證所有的連通域都能夠被遍歷到。

template<typename Tv, typename Te> void graph<Tv, Te>::BFS(int v, int& clock)   //遍歷單個
{
	queue<int> Q;  //頂點快取佇列
	status(v) = DISCOVERED; //標記頂點為已發現
	Q.enqueue(v);  //將當前頂點入隊
	
	while (!Q.empty())   //只要佇列非空,則繼續
	{
		v = Q.dequeue();  //每次選擇一個頂點出隊,遍歷其所有鄰居,檢查是否存在關聯邊
		dTime(v) = ++clock;
		for (int u = firstNbr(v); u >= 0; u = nextNbr(v, u))   //對於頂點v,從頂點集V的最後一個元素開始尋找鄰居(與v存在關聯邊)
		{

			if (status(u) == UNDISCOVERED)  //如果此鄰居頂點尚未發現
			{			cout << "(v,u)" << "(" << v<<"," << u << ")" << endl;
				status(u) = DISCOVERED;  //設定該頂點為已被發現
				type(v, u) = TREE;   //設定邊e(v,u)為TREE(遍歷樹)
				parent(u) = v;       //設定在遍歷樹中頂點u的父親為v
				Q.enqueue(u);        //頂點u入隊
			}
			else  //如果此鄰居頂點已經被發現
			{
				type(v, u) = CROSS;   //設定邊e(v,u)為CROSS(跨邊),不是遍歷樹枝幹
			}
		}
		status(v) = VISITED;   //設定頂點v為已遍歷
	}
}

template<typename Tv, typename Te> void graph<Tv, Te>::bfs(int s)
{
	reset();   //復位所有頂點和已存在邊的狀態為未被發現,未確定
	int clock = 0;  //時間標籤
	int v = s;
	do
	{
		if(status(v)==UNDISCOVERED)
			BFS(v,clock);   //對每個頂點都進行一次單連通域廣度優先搜尋
		v++;
		cout << "v----" << v << endl;
	} while ((v = (++v%n)) != s);
}

效率:若圖G=(V,E)中共有n個頂點和e條邊,則BFS僅需O(n+e)時間。