圖搜索——深度優先與廣度優先

圖搜索的方法有兩種,一種是深度優先,一種是廣度優先

由於這兩種搜索算法在最壞時間的複雜度都接近於窮舉,因此在算法中使用剪枝非常重要。

區別

1)回溯法       =   深度優先   +   剪枝

      分支限界   =   廣度優先   +   剪枝

2)回溯法適用於在求解空間內求得所有解,通過不斷地深入和回溯,每次擴展一個子節點,可以將滿足條件的所有解搜出

       分支限界法適用於在求解空間內求得滿足條件的一個解或最優解,因爲分支限界使用層層擴展的廣度優先搜索,每次擴展出所有的子節點,對已經訪問過的節點不再重複訪問,每一層都以最優關係遞進,因此可以滿足條件的最優解。

3)實現方式上,在深度優先搜索中通常使用遞歸方法對某個節點進行深入挖掘,在得到結果或者到達葉節點後返回並清楚節點的已訪問狀態。廣度優先搜索中需要使用到隊列,將某個節點的子節點按照某種順序加入隊列中並按順序取出,擴展子節點後加入隊列尾部,對到達的節點加上已訪問狀態,這樣保證可以以某種條件只訪問某個節點一次。

剪枝策略

1)最優化剪枝

記錄下當前得到的最優值,如果當前所在的節點已經沒有可能得到比當前最優值更好的結果,提起回溯。在使用最優化剪枝的過程中,關鍵是在第一次得到結果或到達葉子節點後記錄下得到值作爲當前最優值。當後續遍歷樹的過程中,通過比較如果得到比當前最優值更好的值則更新當前最優值,如果在某個節點中通過比較發現當前所在節點在繼續擴展已經無法得到最優解,則剪枝。

2)可行性剪枝

可行性剪枝一般需要根據問題的理解來進行,需要對問題有深刻的理解同時需要敏銳的觀察和思考。對於某個具體問題,當運行到某個節點後根據問題信息可以判斷是不可行則不需要繼續擴展。比較典型的可行性剪枝是奇偶剪枝。在下面的例子中將有所體現。


例子

http://acm.hdu.edu.cn/showproblem.php?pid=1010

Tempter of the Bone

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 40827    Accepted Submission(s): 11053

Problem Description
The doggie found a bone in an ancient maze, which fascinated him a lot. However, when he picked it up, the maze began to shake, and the doggie could feel the ground sinking. He realized that the bone was a trap, and he tried desperately to get out of this maze.
The maze was a rectangle with sizes N by M. There was a door in the maze. At the beginning, the door was closed and it would open at the T-th second for a short period of time (less than 1 second). Therefore the doggie had to arrive at the door on exactly the T-th second. In every second, he could move one block to one of the upper, lower, left and right neighboring blocks. Once he entered a block, the ground of this block would start to sink and disappear in the next second. He could not stay at one block for more than one second, nor could he move into a visited block. Can the poor doggie survive? Please help him.
 
Input
The input consists of multiple test cases. The first line of each test case contains three integers N, M, and T (1 < N, M < 7; 0 < T < 50), which denote the sizes of the maze and the time at which the door will open, respectively. The next N lines give the maze layout, with each line containing M characters. A character is one of the following:
'X': a block of wall, which the doggie cannot enter;
'S': the start point of the doggie;
'D': the Door; or
'.': an empty block.
The input is terminated with three 0's. This test case is not to be processed.

Output
For each test case, print in one line "YES" if the doggie can survive, or "NO" otherwise.
 
Sample Input
4 4 5 S.X. ..X. ..XD .... 3 4 5 S.X. ..X. ...D 0 0 0
 
Sample Output
NO YES
 
對於這一題,因爲需要找到到達一個點的所有時間來判斷是否可行,因此使用深度優先搜索
可行性剪枝:
1)奇偶剪枝
可以把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 但是要求時間是偶數的 都可以直接判斷不可達!
2)搜索到當前節點所用的時間已經大於所給定時間時,不需要繼續搜索

 

代碼如下:

#include<iostream>
#include<string>
using namespace std;
//dx,dy用於表示在地圖中每一步座標的變遷
int dx[]={0,1,0,-1};
int dy[]={-1,0,1,0};
//map用於記錄地圖
char map[7][7];
//visited記錄是否曾經訪問
bool visited[7][7];
//times記錄訪問距離(時間)
int times[7][7];
//記錄是否能在給定的時間準時到達終點
bool success=false;
//n*m維的數組,要求最短時間爲t
int n,m,t;
//si,sj,di,dj分別用於記錄開始和結束位置的座標
int si,sj,di,dj;

//初始化操作
void init(){
	for(int i=0; i<7; i++)
	  for(int j=0; j<7; j++){
		  map[i][j]='\0';
		  visited[i][j]=false;
		  times[i][j]=0;
	  }
}

void dfs_visit(int x,int y){
	//從上下左右四個方向擴展當前節點
	for(int k=0; k<4; k++){
		if(success||times[x][y]>t)
		  return;
		x+=dx[k];
		y+=dy[k];
		//如果符合條件則標記已訪問並更新距離(時間)
		if(0<=x&&x<n&&0<=y&&y<m&&map[x][y]!='X'&&visited[x][y]==false){
			if((x+y+di+dj+t-times[x-dx[k]][y-dy[k]]-1)%2){
				continue;
			}
			visited[x][y]=true;
			times[x][y]=times[x-dx[k]][y-dy[k]]+1;
			//到達終止節點且用時正好爲題目要求
			if(map[x][y]=='D'){
				if(times[x][y]==t)
					success=true;
				visited[x][y]=false;
				continue;
			}
			dfs_visit(x,y);			
			visited[x][y]=false;
		}
		x-=dx[k];
		y-=dy[k];
	}
}

int main(){
	char c;
	while(1){
		success=false;
		cin>>n>>m>>t;
		if(n==0&&m==0&&t==0)
		  break;
		init();
		for(int i=0; i<n; i++){
			for(int j=0; j<m; j++){
				cin>>c;
				if(c=='S'){
					si=i;
					sj=j;
				 }
				 if(c=='D'){
					di=i;
					dj=j;
				 }
				map[i][j]=c;
			}
		}
		//根據起始點和終止點的奇偶性判斷給定的時間是否可行
		if((si+sj+di+dj+t)%2){
			cout<<"NO"<<endl;
			continue;
		}
		int x=si;
		int y=sj;
		int q;
		visited[x][y]=true;
		times[x][y]=0;
		//深度優先搜索
		dfs_visit(x,y);
		if(success)
		 cout<<"YES"<<endl;
		else
		 cout<<"NO"<<endl;
	}
	return 0;
}



插一個小問題,這一題一開始用scanf在hdoj上老是WA,改成cin和cout後成功通過,可能是編譯對字符輸入的問題。

 

如果將這個問題改一下,變成判斷的時間是最優時間,可以改成使用廣度優先方法進行搜索,代碼如下:

#include<stdio.h>
//dx,dy用於表示在地圖中每一步座標的變遷
int dx[]={0,1,0,-1};
int dy[]={-1,0,1,0};
//map用於記錄地圖
char map[7][7];
//visited記錄是否曾經訪問
bool visited[7][7];
//vetor是一個隊列,保存要被擴充的節點
int vetor[50];
//head和tail用於隊列操作
int head=0,tail=0;
//times記錄訪問距離(時間)
int times[7][7];

//初始化操作
void init(){
	for(int i=0; i<7; i++)
	  for(int j=0; j<7; j++){
		  map[i][j]='\0';
		  visited[i][j]=false;
		  times[i][j]=0;
	  }
	for(int k=0; k<50; k++)
	  vetor[k]=0;
}

//插入隊列
void enqueue(int v){
	vetor[tail]=v;
	if(tail==49)
	  tail=0;
	else tail++;
}

//刪除隊列
int dequeue(){
	int x=vetor[head];
	if(head==49)
	  head=0;
	else head++;
	return x;
}

int main(){
	//si,sj,di,dj分別用於記錄開始和結束位置的座標
	int si,sj,di,dj;
	//n*m維的數組,要求最短時間爲t
	int n,m,t;
	char c;
	//記錄是否能在給定的最短時間準時到達終點
	bool success;
	while(1){
		success=false;
		scanf("%d%d%d",&n,&m,&t);
		if(n==0&&m==0&&t==0)
		  break;
		init();
		for(int i=0; i<n; i++){
			scanf("%c",&c);
			for(int j=0; j<m; j++){
				scanf("%c",&c);
				if(c=='S'){
					si=i;
					sj=j;
				 }
				 if(c=='D'){
					di=i;
					dj=j;
				 }
				map[i][j]=c;
			}
		}
		//根據起始點和終止點的奇偶性判斷給定的時間是否可行
		if((si+sj+di+dj+t)%2){
			printf("NO\n");
			continue;
		}
		int x=si;
		int y=sj;
		int q;
		visited[x][y]=true;
		enqueue(x*m+y);
		times[x][y]=0;
		//廣度優先搜索
		while(tail!=head){
			q=dequeue();
			x=q/m;
			y=q%m;
			//從上下左右四個方向擴展當前節點
			for(int k=0; k<4; k++){
				x+=dx[k];
				y+=dy[k];
				//如果符合條件則標記已訪問並更新距離(時間)
				if(0<=x&&x<n&&0<=y&&y<m&&map[x][y]!='X'&&visited[x][y]==false){
					if((x+y+di+dj+t-times[x-dx[k]][y-dy[k]]-1)%2){
						continue;
					}
					visited[x][y]=true;
					times[x][y]=times[x-dx[k]][y-dy[k]]+1;
					//到達終止節點且用時正好爲題目要求
					if(map[x][y]=='D'&×[x][y]==t){
						success=true;
						break;
					}
					enqueue(x*m+y);
				}
				x-=dx[k];
				y-=dy[k];
			}
		}
		if(success)
		  printf("YES\n");
		else
		  printf("NO\n");
	}
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章