算法引入 \color{red}\text{算法引入} 算法引入
小时候,我们会玩一种迷宫游戏。下面就是非常简单的一种:
(本文仅该图来源于网络) \color{grey}\text{(本文仅该图来源于网络)} (本文仅该图来源于网络)
完成该迷宫可以有很多种方法:
-
正向搜索
-
逆向倒推
不管使用哪一种方法,如果遇到走不通的路,我们都会到上一步的位置,然后重新寻找。只要反复地使用这种方法,我们就能够走出迷宫。
在 OI \text{OI} OI中,我们会把迷宫进行抽象化,例如用#
表示障碍,.
表示可走的位置,当然这样的操作只能还原普通的矩形迷宫(当然奇形怪状的迷宫就不行了)。
例如这个迷宫(数字表示长与宽):
3 5
. . # . #
. . . . .
# . . # .
另外,给出迷宫随机生成代码。
假如在这个迷宫中,只能上下左右地移动,那么我们可以通过 ( 1 , 1 ) → ( 2 , 1 ) → ( 2 , 2 ) → ( 2 , 3 ) → ( 2 , 4 ) → ( 2 , 5 ) → ( 3 , 5 ) (1,1)\to (2,1)\to (2,2)\to (2,3)\to (2,4)\to (2,5)\to (3,5) (1,1)→(2,1)→(2,2)→(2,3)→(2,4)→(2,5)→(3,5)的路径抵达终点。
我们可以把迷宫保存在一个二维字符数组 m a p s i , j maps_{i,j} mapsi,j中。对于这四个方向,横纵坐标会做相应的变化:
-
上 → ( − 1 , 0 ) \to (-1,0) →(−1,0)
-
下 → ( 1 , 0 ) \to (1,0) →(1,0)
-
左 → ( 0 , − 1 ) \to (0,-1) →(0,−1)
-
右 → ( 0 , 1 ) \to (0,1) →(0,1)
当然,有的题目还可以使用左上、左下、右上、右下,横纵坐标这时会这样变化:
-
左上 → ( − 1 , − 1 ) \to (-1,-1) →(−1,−1)
-
左下 → ( 1 , − 1 ) \to (1,-1) →(1,−1)
-
右上 → ( − 1 , 1 ) \to (-1,1) →(−1,1)
-
右下 → ( 1 , 1 ) \to (1,1) →(1,1)
为了方便表示,我们可以维护两个一维数组来保存这些横纵坐标的变化量,例如,如果只保存上下左右的话:
int dx[]={
-1,1,0,0},dy[]={
0,0,-1,1};
假如八个方向都保存,则有:
int dx[]={
-1,1,0,0,-1,1,-1,1},dy[]={
0,0,-1,1,-1,-1,1,1};
假设当前在地图中的位置为 ( x , y ) (x,y) (x,y),则我们可以进行一个循环,每一次在 ( x , y ) (x,y) (x,y)的基础上增加 ( d x i , d y i ) (dx_i,dy_i) (dxi,dyi),得到新的位置 ( n x , n y ) = ( x + d x i , y + d y i ) (nx,ny)=(x+dx_i,y+dy_i) (nx,ny)=(x+dxi,y+dyi)。当然,还得考虑边界问题和题目中的障碍等问题。
接下来就粗略地复习一下两种搜索:
深度优先搜索 \color{green}\text{深度优先搜索} 深度优先搜索
第一种搜索叫做深度优先搜索,简称深搜,英文缩写为 DFS \text{DFS} DFS。 DFS \text{DFS} DFS适用于找到第一个符合要求的答案。 DFS \text{DFS} DFS被形象地称为不撞南墙不回头的一根筋。之前的地图问题就适合用该方法。
该方法的模板:
void dfs(参数表)
{
if(到达终点状态)
{
输出或保存结果;
退出该层循环或整个程序;
}
for(拓展方式)
{
if(该步合法)
{
修改、计算、标记;
dfs(新的参数表);
回溯;(可选)
}
}
}
广度优先搜索 \color{green}\text{广度优先搜索} 广度优先搜索
有深度,就有广度。广度优先搜索简称广搜,英文缩写为 BFS \text{BFS} BFS。 BFS \text{BFS} BFS适用于寻找最优解,个人认为要比 DFS \text{DFS} DFS要好用一些,但是需要维护队列,而队列的维护可以有两种:
-
数组维护
-
STL:queue \text{STL:queue} STL:queue 维护
队列用数组维护一般来说效率会高一些,但是要控制空间大小,不宜过小(否则会 RE \text{RE} RE),也不宜过大(否则会 MLE \text{MLE} MLE)。
而用 STL \text{STL} STL中的 queue \text{queue} queue的话,可以不考虑空间,但是时间会略慢一些(恐怕所有 STL \text{STL} STL均是如此)。
数组维护的模板:
队列类型 q;//一般来说队列用q表示
int front=1,rear=1;//先定义头指针和尾指针,一开始一般都为1
while(front<=rear)
{
队列类型 qf=q[front];//为了方便,可以保存头指针的队列元素
for(拓展方式)
{
if(该步合法)
{
修改、计算、标记;
q[++rear]=...;//入队
if(到达终点状态)
{
进行重赋值或直接输出并退出;
}
}
}
front++;//头指针右移
}
queue \text{queue} queue维护的模板:
queue<队列类型>q;
while(q.size())//只要队列中有元素就继续操作
{
队列类型 qf=q.front();//q.front()为头指针元素
q.pop();//直接弹出队头
for(拓展方式)
{
if(该步合法)
{
修改、计算、标记;
q.push(...);//入队
if(到达终点状态)
{
进行重赋值或直接输出并退出;
}
}
}
}
应用与练习 \color{red}\text{应用与练习} 应用与练习
这一次我们将练习下列题目:
题目 | 难度 | 来源 | 算法 |
---|---|---|---|
P1443 马的遍历 \text{P1443 马的遍历} P1443 马的遍历 | 普及/提高- \color{FFC116}\text{普及/提高-} 普及/提高- | 无 | BFS \text{BFS} BFS |
P1135 奇怪的电梯 \text{P1135 奇怪的电梯} P1135 奇怪的电梯 | 普及/提高- \color{FFC116}\text{普及/提高-} 普及/提高- | 无 | BFS \text{BFS} BFS |
P1019 单词接龙 \text{P1019 单词接龙} P1019 单词接龙 | 普及/提高- \color{FFC116}\text{普及/提高-} 普及/提高- | NOIP2000 \texttt{NOIP2000} NOIP2000 | DFS+字符串处理 \text{DFS+字符串处理} DFS+字符串处理 |
U119869 迷宫 \text{U119869 迷宫} U119869 迷宫 |