深度優先搜索DFS和廣度優先搜索BFS簡單解析(新手向)


深度優先搜索DFS和廣度優先搜索BFS簡單解析

與樹的遍歷類似,圖的遍歷要求從某一點出發,每個點僅被訪問一次,這個過程就是圖的遍歷。圖的遍歷常用的有深度優先搜索和廣度優先搜索,這兩者對於有向圖和無向圖均適用。

一.深度優先搜索

1.理解分析

首先,讓我們來看一看更些簡單的深度優先搜索DFS。顧名思義,這個搜索方法是以深度優先,也就是先一條路走到黑,撞到南牆再回頭。我們可以看做是一棵樹,優先走到根部,然后換一根繼續走到最后。下面給出一張圖便於理解。

我們可以看到,我們先從V1出發前往V2,然后繼續往更深的地方出發,前往V5,V9,然后由於V9是根的最深處,於是我們返回上一層(V5所在),發現還有一個V10沒有搜索,所以我們前往V10,然后由於V10是最深的地方,接着往回上一層(V5所在),看看是否還有沒有訪問搜索的點,發現沒有,接着返回上一層(V2所在),發現還有一個V6沒有訪問,於是搜索訪問V6。如此重復,這樣就是深度優先搜索。

我們來仔細思考下這個過程,有沒有發現和遞歸有着類似之處?

我們來看一看,第一次調用,我們可以理解為目前處於第二層搜索V2節點,第二次調用,我們可以理解為DFS里搜索該節點下的第三層節點,調用完成后結束調用可以看做返回第一層,這樣,其中一條V2及以下都已經被遍歷過,重復過程,改變參數,我們可以使得所有都被遍歷過一次。所以,我們一般采用遞歸的方法來實現DFS。

2.例題分析

https://vjudge.net/problem/HDU-1241

油田問題可以說是經典的使用DFS解決的問題了。
Input:

Output:
2
輸入的第一行是油田行數和列數,@符號代表油田,我們要求的是的 @ 符號連成一塊地(橫豎斜相連都算),能有幾塊這樣的油田地。
下面給出代碼:

#include<iostream>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
int map[150][150];//用來記錄該地是否被查詢過,0代表沒有
int x, y;
char p[150][150];//存放油田
int find(int a, int m, int n)
{
	if (map[m][n] != 0|| p[m][n] != '@')//當不是油田(也就是搜索到頭了),並且被找過了,則返回0
		return 0;
	else
	{
		map[m][n] = a;
        //下面這些就是遞歸部分,寫成這樣分開便於理解
		//先一直往橫坐標加1,往右側不斷查詢,以下同理
		find(a, m + 1, n);
		find(a, m + 1, n + 1);
		find(a, m + 1, n - 1);
		find(a, m, n + 1);
		find(a, m, n - 1);
		find(a, m - 1, n + 1);
		find(a, m - 1, n);
		find(a, m - 1, n - 1);
	}
	return 0;
}
int oilPocket(int a)
{
	int i, j;
	for (i = 0; i < x; i++)
	{
		for (j = 0; j < y; j++)
		{
			if (map[i][j] == 0 && p[i][j] == '@')
			{   //當遇到一塊油田沒有被遍歷過,則以這個點進行深度優先搜索
				find(a, i, j);//a代表這塊大油田地是第幾塊,ij就是坐標
				a++;
			}
		}
	}
	return a;
}
int main()
{
	int ans;
	while (scanf("%d %d", &x, &y) != EOF)
	{
		if (x == 0 || y == 0) break;
		memset(p, '*', sizeof(p));
		for (int i = 0; i < x; i++)
		{
			cin.get();//存回車
			for (int j = 0; j < y; j++)
			{
				scanf("%c", &p[i][j]);
			}
		}
		memset(map, 0, sizeof(map));
		ans = oilPocket(1) - 1;
		printf("%d\n", ans);
	}
	return 0;
}

這段代碼主要是find函數部分,我們可以看到,每次遞歸都是一開始往右側找,找到頭后往下找,每次都是有着固定的方向,一路尋找到頭,然后才會改變方向。這樣,我們就使用DFS成功地求得連接在一起的油田數量。

  • 有一點在寫深度優先搜索時容易犯錯誤,那就是注意要判斷該點是否查詢過,反復查詢會導致無限遞歸從而程序出現錯誤。

二.廣度優先搜索

1.理解分析

和深度優先搜索不同的是,我們先訪問的是同一層未被搜索過的點,當該層搜索完畢后,我們才會往下一層進發,開始下一層的搜索。

由圖我們可以看到我們從V1開始,選擇搜索V2,接下來並沒有同深度優先搜索一樣,搜索V5,而是接着看看同層是否有未被搜索過的點,我們發現V3沒有被搜索過,所以我們接着搜索V3,知道V3,V4都被搜索過,我們才開始往下一層進發,搜索V5,V6......

接着,我們來仔細想想,我們該怎么實現BFS呢?

我們發現,如果我們把同一層的點存起來,那么,先進先出的話,同層點在被訪問過后,才會接着訪問下一層的點。而先進先出正是隊列的特點,所以,我們可以使用隊列來實現BFS。

2.例題分析

https://vjudge.net/problem/HDU-1548

奇怪的電梯,可以使用BFS來解決,當然也可以使用DFS和Dijkstra
來解決(有興趣可以嘗試下)

奇怪的電梯題目輸入是當前所在樓層,目的樓層,樓層總數,每層電梯只能上下的層數。
輸出是最少的次數,不能到達則-1。

#include <queue>
#include <iostream>
#include <cstring>
using namespace std;
queue <int> q;
int num,s,e;
int a[1000];
int step[1000];
void bfs(){
    int m,n;
    while(!q.empty())
    {
        m = q.front();//取出當前隊列第一個數,即當前樓層
        q.pop();
        n = m + a[m];//往上移動到的樓層數
        if(n >= 1 && n <= num && step[n] == -1)
        {
            step[n] = step[m] + 1;//步數自增
            q.push(n);//把當前(移動后的)樓層數加入隊列
        }
        n = m - a[m];//往下移動到的樓層數
        if(n >= 1 && n <= num && step[n] == -1)
        {
            step[n] = step[m] + 1;//步數自增
            q.push(n);//把當前(移動后的)樓層數加入隊列
        }
    }
    //隊列為空,則表示沒有可以移動的位置了,即所有能走的樓層均走過
    cout << step[e] << endl;
}

int main(){
    while(scanf("%d",&num)!= EOF)
    {
        if(num == 0)
        {
            break;
        }
        scanf("%d%d",&s,&e);//開始結束樓層
        for(int i = 1 ; i <= num ; i++)
        {
            scanf("%d",&a[i]);//每層固定上下移動的層數
        }
        memset(step,-1,sizeof(step));
        step[s] = 0;
        q.push(s);//將開始層數加入隊列
        bfs();
    }
    return 0;
}

BFS對於求無權路的最短路徑很方便,遍歷一遍,到了對應的節點,則可以說是最短路徑,對於有權圖可以采用Dijkstra等等。

三.總結

BFS和DFS主要是一種遍歷圖的方式,理解透徹具體是什么,該怎么遍歷,熟練之后便可以很快上手,那么該怎么熟練呢?當然是理解+刷題(笑。再來推薦道題目Serial Time! ,DFS,BFS都可以嘗試下(逃


免責聲明!

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



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