簡單易懂的深度優先搜索算法(DFS)

在我們遇到的一些問題當中,有些問題我們不能夠確切的找出數學模型,即找不出一種直接求解的方法,解決這一類問題,我們一般採用搜索的方法解決。搜索就是用問題的所有可能去試探,按照一定的順序、規則,不斷去試探,直到找到問題的解,試完了也沒有找到解,那就是無解,試探時一定要試探完所有的情況(實際上就是窮舉)。

對於問題的第一個狀態,叫初始狀態,要求的狀態叫目標狀態。
搜索就是把規則應用於實始狀態,在其產生的狀態中,直到得到一個目標狀態爲止。

感謝杭電劉老師的ppt

什麼是深度優先搜索

所有的搜索算法從其最終的算法實現上來看,都可以劃分成兩個部分──控制結構和產生系統。正如前面所說的,搜索算法簡而言之就是窮舉所有可能情況並找到合適的答案,所以最基本的問題就是羅列出所有可能的情況,這其實就是一種產生式系統。

從根開始計算,到找到位於某個節點的解,回溯法(深度優先搜索)作爲最基本的搜索算法,其採用了一種“一隻向下走,走不通就掉頭”的思想(體會“回溯”二字),相當於採用了先根遍歷的方法來構造搜索樹。

借用劉老師的話

基本思想:從初始狀態S開始,利用規則生成搜索樹下一層任一個結點,檢查是否出現目標狀態G,若未出現,以此狀態利用規則生成再下一層任一個結點,再檢查是否爲目標節點G,若未出現,繼續以上操作過程,一直進行到葉節點(即不能再生成新狀態節點),當它仍不是目標狀態G時,回溯到上一層結果,取另一可能擴展搜索的分支。生成新狀態節點。若仍不是目標狀態,就按該分支一直擴展到葉節點,若仍不是目標,採用相同的回溯辦法回退到上層節點,擴展可能的分支生成新狀態,…,一直進行下去,直到找到目標狀態G爲止。

DFS算法

  • 把起始節點S線放到OPEN表中。
  • 如果OPEN是空表,則失敗退出,否則繼續。
  • 從OPEN表中取最前面的節點node移到CLOSED 表中。
  • 若node節點是葉結點(若沒有後繼節點),則轉向(2)。
  • 擴展node的後繼節點,產生全部後繼節點,並把他們放在OPEN表的前面。各後繼結點指針指向node節點。
  • 若後繼節點中某一個是目標節點,則找到一個解,成功退出。否則轉向(2)循環。

DFS最重要的就是回溯,它的本質是遞歸!

我認爲實質就是暴力枚舉多種可能。

減枝

對於深度優先搜索來說減枝也是極爲重要的。如果遍歷了一些無關緊要的節點的話就會很浪費時間,如果數據小的話還看不出。一旦數據大,減枝所帶來的優化將變得極爲重要。

HDU-1010-Tempter of the Bone爲例

題目

小狗在一個古老的迷宮中發現了一根骨頭,這使他非常着迷。但是,當他撿起它時,迷宮開始搖晃,小狗可以感覺到地面下沉。他意識到骨頭是一個陷阱,他拼命試圖擺脫這個迷宮。

迷宮是一個矩形,大小爲N×M。迷宮中有一扇門。剛開始時,門是關閉的,它將在第T秒打開一小段時間(少於1秒)。因此,小狗必須在第T秒精確到達門。每秒鐘,他可以將一個塊移動到上,下,左和右相鄰的塊之一。一旦他進入一個街區,該街區的地面將開始下沉並在下一秒消失。他不能在一個街區停留超過一秒鐘,也不能搬到一個拜訪的街區。可憐的小狗可以生存嗎?請幫助他。

輸入

輸入包含多個測試用例。每個測試用例的第一行包含三個整數N,M和T(1<N,M<7;0<T<50),分別表示迷宮的大小和門打開的時間。接下來的N行給出迷宮佈局,每行包含M個字符。角色是以下字符之一:

‘X’:小狗無法進入的牆塊;
‘S’:小狗的起點;
‘D’:門;或
“.”:空白塊。

輸入以三個0終止。該測試用例將不被處理。

輸出

對於每個測試用例,如果小狗可以存活,則在一行中打印“YES”,否則打印“NO”。

樣例輸入

4 4 5
S.X.
…X.
…XD

3 4 5
S.X.
…X.
…D
0 0 0

樣例輸出

NO
YES

典型的迷宮式搜索,每一步都只能走一次,並且只有時間剛剛好時,才能成功!

深搜代碼(DFS)

無減枝版本

#include<bits/stdc++.h>
using namespace std;
char Map[15][15];
int n,m,t,qx,qy,zx,zy;
int flag;
int dir[4][2]={-1,0,0,-1,1,0,0,1};//上下左右四個方向 
void dfs(int x,int y,int cnt){      //搜索
	if(x==zx&&y==zy&&cnt==t){   //如果位置和出口一樣並且時間一樣則爲成功
		flag=1;
	}
	if(flag){               //只要有一次成功則直接return
		return;
	}   
	if(cnt>t){              //如果時間超出限制
		return;
	}
	for(int i=0;i<4;i++){       //四個方向尋求可以走的地方
		int xx=x+dir[i][0];
		int yy=y+dir[i][1];
		if(xx<0||yy<0||xx>n-1||yy>m-1){     //如果超出地圖
			continue;
		}
		else if(Map[xx][yy]!='X'){      //如果可以走
			Map[xx][yy]='X';            //標記爲下次不能走
			dfs(xx,yy,cnt+1);       //進入搜索
			Map[xx][yy]='.';        //取消標記
		}
	}
}
int main(){
	while(scanf("%d %d %d",&n,&m,&t)!=EOF&&(n||m||t)){
		for(int i=0;i<n;i++){
			scanf("%s",Map[i]);
			for(int j=0;j<m;j++){
				if(Map[i][j]=='S'){
					qx=i;
					qy=j;
				}
				if(Map[i][j]=='D'){
					zx=i;
					zy=j;
				}
			}
		}
		flag=0;
		Map[qx][qy]='X';    //將起點標記爲不能走
		dfs(qx,qy,0);       /進入搜索
		if(flag){
			printf("YES\n");
		}
		else{
			printf("NO\n"); 
		}
	}
	return 0;
}

如果沒有減枝也能搜索出來,但是會超時,因爲浪費了很多不必要的時間

廣度和深度優先搜索有一個很大的缺陷,就是他們都是在一個給定的狀態空間中窮舉。這在狀態空間不大的情況下是很合適的算法,可是當狀態空間十分大,且不預測的情況下就不可取了。他的效率實在太低,甚至不可完成。

所以,在這裏再次強調“剪枝”!

減枝

可以把map看成這樣:

  • 0 1 0 1 0 1
  • 1 0 1 0 1 0
  • 0 1 0 1 0 1
  • 1 0 1 0 1 0
  • 0 1 0 1 0 1

從爲 0 的格子走一步,必然走向爲 1 的格子
從爲 1 的格子走一步,必然走向爲 0 的格子
即:

0->1或1->0 必然是奇數步
0->0 走1->1 必然是偶數步

所以當遇到從 0 走向 0 但是要求時間是奇數的,或者, 從 1 走向 0 但是要求時間是偶數的 都可以直接判斷不可達!

則我們可以,判斷他終點和起點是否一致。來進行減枝,減去一些不必要浪費的時間

僞代碼

int sum=t-abs(zx-qx)-abs(zy-qy);
if(sum>=0&&sum%2==0){
	dfs(qx,qy,0);
}

完整代碼

#include<bits/stdc++.h>
using namespace std;
char Map[15][15];
int n,m,t,qx,qy,zx,zy;
int flag;
int dir[4][2]={-1,0,0,-1,1,0,0,1};//上下左右四個方向 
void dfs(int x,int y,int cnt){
	if(x==zx&&y==zy&&cnt==t){
		flag=1;
	}
	if(flag){
		return;
	}
	if(cnt>t){
		return;
	}
	for(int i=0;i<4;i++){
		int xx=x+dir[i][0];
		int yy=y+dir[i][1];
		if(xx<0||yy<0||xx>n-1||yy>m-1){
			continue;
		}
		else if(Map[xx][yy]!='X'){
			Map[xx][yy]='X';
			dfs(xx,yy,cnt+1);
			Map[xx][yy]='.';
		}
	}
}
int main(){
	while(scanf("%d %d %d",&n,&m,&t)!=EOF&&(n||m||t)){
		for(int i=0;i<n;i++){
			scanf("%s",Map[i]);
			for(int j=0;j<m;j++){
				if(Map[i][j]=='S'){
					qx=i;
					qy=j;
				}
				if(Map[i][j]=='D'){
					zx=i;
					zy=j;
				}
			}
		}
		flag=0;
		Map[qx][qy]='X';
		int sum=t-abs(zx-qx)-abs(zy-qy);
		if(sum>=0&&sum%2==0){
			dfs(qx,qy,0);
		}
		if(flag){
			printf("YES\n");
		}
		else{
			printf("NO\n"); 
		}
	}
	return 0;
} 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章