目录
当我们需要在一个范围内逐步的寻找某个特定的元素,就需要使用搜索方法。 今天主要讲解深搜和广搜。
一.深搜:
1.深搜的定义:
深搜就是按一种方式走到底。如果类比为战争,深搜就是派出一名先锋,杀入敌人内部,进行斩首行动,所以他的运动方向是单一的,不能多个方向同时进行,他一头扎进去,一次性把敌人的版图走完,要么找到目标,要么失败。所以深搜的精髓在于按一定顺序前进和发现路走不通时进行回溯。 回溯的定义就是退到上一个状态,并向其他方向走。如果继续用上面的例子,就相当于战士在一个分岔口向左拐后发现是一个死胡同,就需要回到上一个分岔口,然后向右拐。
2.深搜的步骤:
(1).用二维数组将地图上的障碍等特殊位置标注出来,并开二位数组去记录每个点是否走过。
(2).用两个一维数组或一个二位数组定义下一步的方向(后面例题会用讲解)
(3).从起点开始向下一步走,使用for循环,判断是否越界,是否是障碍物,是否符合条件。
(4).如果符合条件,则将该点标记为已走过,对这个点进行dfs,并做好回溯操作,即将标记取消。(例题中有详解)
(5).在主函数中,将起点记为已走过(否则会死循环!!!!)
3.例题详解:
很正常的一道深搜题目,按照上面的方式分析:
int n,m,t,sx,sy,fx,fy,sum;
bool zh[10][10],use[10][10];
(1).设置二维数组,标记是否是障碍物,是否已经走过。
int dx[4]={0,0,1,-1};
//下一步x的变化
int dy[4]={-1,1,0,0};
//下一步y的变化
for(int i=0;i<=3;i++){
int nx,ny;
nx=x+dx[i];
ny=y+dy[i];
//nx,ny是下一个点的坐标
(2).设置下一步走的方向,这里用了两个一维数组,分别定义x和y的方向。使用for循环,向上下左右进行前进。
if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&zh[nx][ny]==0&&use[nx][ny]==0)
(3).判断是否符合要求。
{
use[nx][ny]=1;
dfs(nx,ny);
use[nx][ny]=0;
}
(4).如果符合要求,就继续深搜,并将标记点消去,以便回溯可以进行。
int main()
{
cin>>n>>m>>t;
cin>>sx>>sy>>fx>>fy;
for(int i=1;i<=t;i++)
{
int x,y;
cin>>x>>y;
zh[x][y]=1;
}
use[sx][sy]=1;
//将起始点标记为已走过
dfs(sx,sy);
cout<<sum;
return 0;
}
(5) .在主函数中列出地图,并将起始点标记为已走过。
完 整 代 码:
#include<iostream>
using namespace std;
int n,m,t,sx,sy,fx,fy,sum;
bool zh[10][10],use[10][10];
int dx[4]={0,0,1,-1};
int dy[4]={-1,1,0,0};
void dfs (int x,int y)
{
if(x==fx&&y==fy)
{
sum++;
return ;
}
for(int i=0;i<=3;i++){
int nx,ny;
nx=x+dx[i];
ny=y+dy[i];
if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&zh[nx][ny]==0&&use[nx][ny]==0)
{
use[nx][ny]=1;
dfs(nx,ny);
use[nx][ny]=0;
}
}
}
int main()
{
cin>>n>>m>>t;
cin>>sx>>sy>>fx>>fy;
for(int i=1;i<=t;i++)
{
int x,y;
cin>>x>>y;
zh[x][y]=1;
}
use[sx][sy]=1;
dfs(sx,sy);
cout<<sum;
return 0;
}
二 .广搜
1.广搜含义:
广搜就是多个方向同时前进,直到搜索到所需要的目标。依然类比一场战争,广搜就是同时派出多支部队,同时向不同的方向行进,直到有一支队伍率先找到目标,其他部队停止追击(或者继续前进)。 所以广搜的精髓在于所有方向同时前进,为了实现这个精髓,应当使所有同一阶段的点先移动完成,然后再让他产生的点移动,所以自然想到使用队列,每次弹出首元素,就实现了先进先出。
2.广搜步骤:
(1).开数组记录是否走过该点,设置前进的方向,同深搜,如:上下左右.......;并定义结束条件。
(2).设置能包含所需元素的队列(这个地方会影响后续的复杂程度),并将第一个元素压入队列中。
(3)当队列非空,取出首元素作为作为下一个移动对象,并在队列中删除首元素(以防多次操作)。.
(4).使用for循环移动,如果不越界且未走过,则将其压入队列,并标记为走过(同样是防止一个点走多次)。
.
3.广搜例题:
按照以上步骤进行:
(1).开数组记录是否走过该点,设置前进的方向,同深搜,如:上下左右.......;并定义结束条件。
int mp[1005][1005],value[1005][1005];
int m,n,ex,ey,sx,sy;
int nx,ny;
int next_x;
int next_y;
//mp数组记录图上的点
//value函数记录到每一个点所需的最小步数
int movx[8]={1,2,2,1,-1,-2,-2,-1};
int movy[8]={2,1,-1,-2,-2,-1,1,2};
//两个数组,一个表示x轴移动,一个是y轴的移动
memset(mp,0,sizeof(mp));
memset(value,-1,sizeof(value));
//初始化
(2).设置能包含所需元素的队列(这个地方会影响后续的复杂程度),并将第一个元素压入队列中。
queue <pair<int,int> > p;
mp[x][y]=1;
value[x][y]=0;
//将第一个点初始化
p.push(make_pair(x,y));
//将第一个点压入队列
这里我是用pair与queue结合,直接压入x与y两个值。
pair头函数是utility,关于vector和pair的相关使用可以参考我上一篇文章的万字解析。
(3)当队列非空,取出首元素作为作为下一个移动对象,并在队列中删除首元素(以防多次操作)。.
while(!p.empty()){
nx=p.front().first;
ny=p.front().second;
//取出第一个元素
p.pop();
//将第一个元素从队列中取出
(4).使用for循环移动,如果不越界且未走过,则将其压入队列,并标记为走过(同样是防止一个点走多次)。
for(int i=0;i<8;i++){
next_x=nx+movx[i];
next_y=ny+movy[i];
if(next_x>=0&&next_y>=0&&next_y<n&&next_x<m&&mp[next_x][next_y]==0){
//判断条件
p.push(make_pair(next_x,next_y));
//成立就压入队列
mp[next_x][next_y]=1;
//这一点的数值由上一点得来
value[next_x][next_y]=value[nx][ny]+1;
}
}
我这道题是相当简单的题,只用判断是否越界,其他情况因题而异。
完整代码:
//主函数:
//1.列每一点情况
//2.引用bfs,并输出答案
//
//bfs:
//1.设置走位方向,设置结束标识
//2.设置queue与pair,存储二维坐标
//3.判断空栈,出栈,移位,判断,入栈
#include<iostream>
#include<utility>
#include<queue>
#include<string.h>
using namespace std;
int mp[1005][1005],value[1005][1005];
int m,n,ex,ey,sx,sy;
int nx,ny;
int next_x;
int next_y;
void bfs(int x,int y){
int movx[8]={1,2,2,1,-1,-2,-2,-1};
int movy[8]={2,1,-1,-2,-2,-1,1,2};
memset(mp,0,sizeof(mp));
memset(value,-1,sizeof(value));
queue <pair<int,int> > p;
mp[x][y]=1;
value[x][y]=0;
p.push(make_pair(x,y));
while(!p.empty()){
nx=p.front().first;
ny=p.front().second;
p.pop();
for(int i=0;i<8;i++){
next_x=nx+movx[i];
next_y=ny+movy[i];
if(next_x>=0&&next_y>=0&&next_y<n&&next_x<m&&mp[next_x][next_y]==0){
p.push(make_pair(next_x,next_y));
mp[next_x][next_y]=1;
value[next_x][next_y]=value[nx][ny]+1;
}
}
}
}
int main(){
cin>>m>>n;
cin>>sx>>sy;
bfs(sx-1,sy-1);
for(int i=0;i<m;i++){
for(int j=0;j<n-1;j++){
cout<<value[i][j]<<" ";
}
cout<<value[i][n-1]<<endl;
}
return 0;
}
今天介绍的两道题比较简单,是对深搜,广搜的直接应用。但无论搜索相关的题目有多复杂,本质都是要有如上的两种思考过程,在这个框架基础上完成自己特定目标。
今天的分享就这么多了,有疑问的地方欢迎评论区交流。