图搜索——深度优先与广度优先

图搜索的方法有两种,一种是深度优先,一种是广度优先

由于这两种搜索算法在最坏时间的复杂度都接近于穷举,因此在算法中使用剪枝非常重要。

区别

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");
	}
}



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