双端队列广搜
定义
双端队列广搜(Breadth-First Search with a Deque)是一种图或树的遍历算法变体,它利用了双端队列(Deque,全称Double Ended Queue,允许在其两端进行插入和删除操作)作为数据结构来存储待探索的节点。与传统使用队列的BFS相比,双端队列BFS能够在某些特定问题中更高效地找到最短路径,尤其是当存在多条路径长度相等且需要找到特定类型的目标节点时。
运用情况
- 特定目标查找:当目标具有某种特性,且希望在找到第一个满足条件的节点时立即停止搜索。
- 无权图的最短路径问题:当图中的边没有权重,或者所有边的权重都相同时,可以用来快速找到两个节点间的最短路径。
- 游戏中玩家的移动策略:例如在棋盘游戏中寻找最短的移动步数到达目标位置,尤其是在每次移动代价相同的情况下。
注意事项
- 初始化:确保从源节点开始,将其放入双端队列的前端。
- 层次遍历:为了保证层次遍历的特性,新扩展的节点应当被添加到双端队列的末端,而当前处理的节点通常从队列的前端取出。
- 标记已访问:避免重复访问同一个节点,对于每个访问过的节点都要做标记。
- 目标检测:在每次循环开始前检查双端队列前端的节点是否为目标节点,如果是,则可以提前结束搜索。
- 剪枝:根据具体问题,适时进行剪枝操作可以减少不必要的探索,提高效率。
解题思路
- 初始化:创建一个双端队列,并将起始节点入队。
- 循环遍历:当双端队列非空时,进行循环。
- 出队:从队列前端取出一个节点。
- 检查目标:如果取出的节点是目标节点,记录结果并结束循环。
- 扩展节点:对取出节点的所有邻居进行操作,若邻居未被访问过,则标记为已访问,并根据需要将其加入队列的末端。
- 处理结果:如果找到了目标节点,根据题目需求输出路径或步数;如果没有找到,则输出相应的失败信息。
AcWing 175. 电路维修
题目描述
AcWing 175. 电路维修 - AcWing
运行代码
#include <cstring>
#include <iostream>
#include <algorithm>
#include <deque>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 510, M = N * N;
int n, m;
char g[N][N];
int dist[N][N];
bool st[N][N];
int bfs()
{
memset(dist, 0x3f, sizeof dist);
memset(st, 0, sizeof st);
dist[0][0] = 0;
deque<PII> q;
q.push_back({0, 0});
char cs[] = "\\/\\/";
int dx[4] = {-1, -1, 1, 1}, dy[4] = {-1, 1, 1, -1};
int ix[4] = {-1, -1, 0, 0}, iy[4] = {-1, 0, 0, -1};
while (q.size())
{
PII t = q.front();
q.pop_front();
if (st[t.x][t.y]) continue;
st[t.x][t.y] = true;
for (int i = 0; i < 4; i ++ )
{
int a = t.x + dx[i], b = t.y + dy[i];
if (a < 0 || a > n || b < 0 || b > m) continue;
int ca = t.x + ix[i], cb = t.y + iy[i];
int d = dist[t.x][t.y] + (g[ca][cb] != cs[i]);
if (d < dist[a][b])
{
dist[a][b] = d;
if (g[ca][cb] != cs[i]) q.push_back({a, b});
else q.push_front({a, b});
}
}
}
return dist[n][m];
}
int main()
{
int t;
cin >> t;
while(t -- )
{
cin >> n >> m;
for(int i = 0; i < n; i ++ ) cin >> g[i];
int t = bfs();
if(t == 0x3f3f3f3f) puts("NO SOLUTION");
else cout << t << endl;
}
return 0;
}
代码思路
-
定义变量与初始化:定义了二维数组
g
来存储电路板状态,dist
来存储从起点到各点的最小旋转次数(初始化为一个极大值),布尔数组st
用于标记是否访问过某点。cs
数组用于匹配当前点的电缆方向与期望方向的差异,dx
、dy
、ix
、iy
数组用于计算邻居节点坐标。 -
双端队列BFS:使用双端队列来存储待访问的节点,其中队首优先处理沿当前电缆方向前进的情况(无需额外旋转),队尾处理需要额外旋转的情况。这样可以保证在可能的情况下优先找到更优路径。
-
状态更新与路径计算:在BFS过程中,计算到达每个新节点的最小旋转次数,并根据当前点与期望方向的匹配情况决定是将新节点加入队首还是队尾。
-
主循环与输出:循环读取测试用例,对于每个电路板执行BFS,最后输出最小旋转次数或“NO SOLUTION”。
改进思路
-
内存优化:虽然原始代码已经相对高效,但是可以考虑在
bfs
函数内部使用局部变量代替全局变量,以减少内存占用和潜在的变量污染。 -
输入验证:在读取
n
和m
之前,增加对t
的范围验证,确保输入合法。 -
代码清晰性:通过增加注释和改善变量命名,提高代码的可读性。
-
错误处理:在读取电路板状态时,增加输入验证,确保数据有效。