搜索
搜索主要有三类:枚举算法、深度优先搜索和广(宽)度优先搜索。这里我们主要介绍深度深度优先搜索和广(宽)度优先搜索。
深度优先搜索(DFS)
深度优先搜索类似于走迷宫,遇到“分岔口”时选择一个没有走过的路往前“试探”,走得通,就一直往下走,最终走到终点(得到一个正确解),走不通,就回头,直到找到正确路线到达终点(得到一个正确解)或是发现此迷宫无法走出(问题无解)。
这里有两个术语:
状态:在DFS中一般把从做完一次选择到下一次做选择这之间整体地视为一个状态,每做完一次选择,就意味着我们从上一状态转移到了下一状态。仍用迷宫为喻,迷宫的入口就是初始状态(T₀),出口就是目标状态,一个一个路口是中间状态。
产生式系统:状态在程序中该如何表示?从上一状态转移到了下一状态的规则是什么?如何进行状态转移?产生式系统将解决上述问题。我们在运用DFS时,最主要的就是构建一个产生式系统。
深度优先搜索可以与其它算法相结合并可以分为典型的几类,也可以通过一些方法进行优化。在这里并不详细的讨论这些,仅给出一个深度优先搜索的框架。
深度优先搜索框架
void dfs(int k) //代表第k个状态,或者说前进的深度
{
if(到达终点或者目的地)
{
输出问题的解或者解的方案数+;;
}
else
{
for(int i=0;i<可选则的总数;i++) //枚举
{
根据选择进行相应操作,产生新的状态;
if(新的状态合法)
{
保存;
dfs(k+1); //前进,进入下一状态
恢复; //回溯,使用了递归
}
}
}
}
注意:
(1)dfs传入的参数在实际应用中可能不止一个,有时要包括其他的状态信息。
(2)枚举是前进进入下一状态的策略。明确所有操作,每一次选择后要进行的操作要在分析问题时分析清楚。
(3)判断新的状态是否合法时,可以先写一个函数,用调用函数的方式来判断。合法性判断往往是边界判定、判重、逻辑判定等。
(4)回溯时的恢复很重要!保存与恢复是对应的,没有恢复,搜索的合法性解将改变。
具体见下面例题。
优势
由上面的讨论可以看出,DFS利于解决全部解问题,并能在深搜过程中找到”最优“解。
广(宽)度优先搜索(BFS)
DFS是沿着一条路一直向下找,在每个路口一旦找到一个可行的选择便会向下走,找完一遍再重头来。广(宽)度优先搜索(BFS)则是按”层“找,进行地毯式搜索。每次找都要找完一个路口所有的可行选择,最后几乎同时找到所有的可行解。
为了实现这一想法,我们需要用到queue结构。同样,在这里我们给出广(宽)度优先搜索的框架。
广(宽)度优先搜索的框架
void bfs(初始状态(起点)参数)
{
queue<stru>q; //定义队列q,stru为这里假定的类型名
stru t;
将初始状态(起点)参数存入t;
q.push(t); //t入队列
while(!q.empty()) //队列非空时继续搜索,否则结束
{
stru v=q.front(); //取队首元素
q.pop();
if(v是终点)
return;
else
{
for(int i=0;i<可选则的总数;i++) //枚举
{
根据选择进行相应操作,产生新的状态;
if(新的状态合法)
{
stru w;
将新的状态的各个值存入w中;
q.push(w); //w入队列
}
}
}
}
}
实际应用中还有许多要注意的地方,要根据具体情况进行调整。
具体见下面例题。
优势
由上面的讨论可以看出,BFS利于解决最值问题。queue的结构很容易找到最先和最后。
下面分别看一道DFS和一道BFS,帮助大家理解运用。
迷宫
题目描述
给定一个 N × M N \times M N×M 方格的迷宫,迷宫里有 T T T 处障碍,障碍处不可通过。
在迷宫中移动有上下左右四种方式,每次只能移动一个方格。数据保证起点上没有障碍。
给定起点坐标和终点坐标,每个方格最多经过一次,问有多少种从起点坐标到终点坐标的方案。
输入格式
第一行为三个正整数 N , M , T N,M,T N,M,T,分别表示迷宫的长宽和障碍总数。
第二行为四个正整数 S X , S Y , F X , F Y SX,SY,FX,FY SX,SY,FX,FY, S X , S Y SX,SY SX,SY 代表起点坐标, F X , F Y FX,FY FX,FY 代表终点坐标。
接下来 T T T 行,每行两个正整数,表示障碍点的坐标。
输出格式
输出从起点坐标到终点坐标的方案总数。
样例 #1
样例输入 #1
2 2 1
1 1 2 2
1 2
样例输出 #1
1
提示
对于 100 % 100\% 100% 的数据, 1 ≤ N , M ≤ 5 1 \le N,M \le 5 1≤N,M≤5, 1 ≤ T ≤ 10 1 \le T \le 10 1≤T≤10, 1 ≤ S X , F X ≤ n 1 \le SX,FX \le n 1≤SX,FX≤n, 1 ≤ S Y , F Y ≤ m 1 \le SY,FY \le m 1≤SY,FY≤m。
代码
题干中“每个方格最多经过一次”这一类条件在很多DFS、BFS题中常有,这要求我们在初始化、判断、选择后的操作等环节要注意进行相应的操作。这里我用了一个gezi[10][10]的数组来进行记录和辅助判断。要格外小心给起点记录已经过。
#include <bits/stdc++.h>
using namespace std;
int s=0,n,m;
int sx,sy,fx,fy;
int gezi[10][10];
int xy[4][2]={{0,1},{0,-1},{-1,0},{1,0}};
bool judge(int x,int y)
{
if(x>0&&x<=n&&y>0&&y<=m&&gezi[x][y]==0)return true;
else return false;
}
void dfs(int x,int y)
{
if(x==fx&&y==fy)
{
s++;
}
else
{
for(int i=0;i<4;i++)
{
int xx=x+xy[i][0];
int yy=y+xy[i][1];
if(judge(xx,yy))
{
gezi[xx][yy]=1; //保存
dfs(xx,yy);
gezi[xx][yy]=0; //回溯时的恢复
}
}
}
}
int main()
{
int t;
cin>>n>>m>>t;
cin>>sx>>sy>>fx>>fy;
while(t--)
{
int x,y;
cin>>x>>y;
gezi[x][y]=1;
}
//!!!!!!!!!!!!!!
gezi[sx][sy]=1;
dfs(sx,sy);
cout<<s;
return 0;
}
马的遍历
题目描述
有一个 n × m n \times m n×m 的棋盘,在某个点 ( x , y ) (x, y) (x,y) 上有一个马,要求你计算出马到达棋盘上任意一个点最少要走几步。
输入格式
输入只有一行四个整数,分别为 n , m , x , y n, m, x, y n,m,x,y。
输出格式
一个 n × m n \times m n×m 的矩阵,代表马到达某个点最少要走几步(不能到达则输出 − 1 -1 −1)。
样例 #1
样例输入 #1
3 3 1 1
样例输出 #1
0 3 2
3 -1 1
2 1 4
提示
数据规模与约定
对于全部的测试点,保证 1 ≤ x ≤ n ≤ 400 1 \leq x \leq n \leq 400 1≤x≤n≤400, 1 ≤ y ≤ m ≤ 400 1 \leq y \leq m \leq 400 1≤y≤m≤400。
代码
题干中很明显地出现了“最少”两个字,且要求求出所有点的最少步。虽然DFS也可以解决,但BFS显得更快捷简单。这其中如何标记已走过的点(因为要最少)和如何停止已找到最少步的点的继续搜索是需要思考的。
意识到了吗?标记已走过的点和停止已找到最少步的点的继续搜索其实是一回事,因为已走过的点就是已找到最少步的点!
#include <bits/stdc++.h>
using namespace std;
int n,m;
int Min[405][405];
int xy[8][2]={{1,2},{2,1},{2,-1},{1,-2},{-1,-2},{-2,-1},{-2,1},{-1,2}};
struct x_y
{
int x,y,step;
};
int check[405][405];
bool judge(int x,int y)
{
if(x>0&&x<=n&&y>0&&y<=m&&check[x][y]==0)return true;
else return false;
}
void bfs(int x,int y)
{
queue<x_y>q;
x_y t;
t.x=x,t.y=y,t.step=0;
check[x][y]=1;
q.push(t);
while(!q.empty())
{
x_y z=q.front();
q.pop();
Min[z.x][z.y]=z.step;
for(int i=0;i<8;i++)
{
int xx=z.x+xy[i][0];
int yy=z.y+xy[i][1];
if(judge(xx,yy))
{
x_y w;
w.x=xx,w.y=yy;
w.step=z.step+1;
check[xx][yy]=1;
q.push(w);
}
}
}
}
int main()
{
memset(Min,-1,sizeof(Min));
int x,y;
cin>>n>>m;
cin>>x>>y;
bfs(x,y);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
cout<<Min[i][j]<<' ';
if(j==m)cout<<'\n';
}
return 0;
}