简介
这个算法只适用于处理边权仅为 0 或 1 的图
相比于通用的 Dijkstra 算法,01BFS 的时间复杂度更优,能达到 O(V+E)(V是顶点数,E是边数),且实现起来更加轻简洁。
核心
Dijkstra 的性能瓶颈在于维护有序状态的优先队列(堆),其入队操作带有 $\log$ 级别的时间开销。而在 0-1 BFS 中,我们可以利用双端队列(Deque)来人工维护这种单调性:
- 权值为 0 的边:意味着发现了一条“免费”路径,该节点应通过
push_front插入队首,确保它排在所有权值为 1 的节点之前被优先探索。 - 权值为 1 的边:遵循常规 BFS 逻辑,通过
push_back插入队尾。
这种策略本质上是 Dijkstra 的特例:通过双端队列确保了队列中的距离序列始终满足非递减性质,从而规避了堆排序的开销。
实现细节
- 更新时机与冗余检查:在普通 BFS 中,节点入队即标记
visited;但在 0-1 BFS 中,一个节点可能先被权 1 边发现,随后又被权 0 边刷新出更短距离。因此,建议在入队前判断if (new_dist < dist[v]),只有当发现更优路径时才允许入队。 - 出队检查:为了彻底消除冗余计算,节点出队时应检查当前距离是否仍为
dist数组中的最优值,若已被之前的操作更新,则直接跳过。 - 空间稳定性:在 0-1 BFS 下,每个节点最多只会被更新两次(一次由权 1 边触发,一次由权 0 边触发),这保证了算法在空间占用上的可控性。
示例题目
#include <bits/stdc++.h>
#define endl "\n"
#define rep(i, a, b) for (i64 i = a; i <= b; i++)
#define lep(i, a, b) for (i64 i = a; i >= b; i--)
// #define MOD
using namespace std;
using i64 = int64_t;
using ui64 = uint64_t;
using vi64 = vector<i64>;
// static const i64 MOD = ;
int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
i64 n, m;
while (1) {
cin >> n >> m;
if (n == 0 && m == 0)
break;
vector<string> go(n);
rep(i, 0, n - 1) { cin >> go[i]; }
i64 sx, sy, ex, ey;
cin >> sx >> sy >> ex >> ey;
vector<vector<i64>> d(n, vector<i64>(m, LLONG_MAX));
deque<pair<i64,pair<i64, i64>>> dq;
d[sx][sy] = 0;
dq.push_back({0,{sx, sy}});
while (dq.size()) {
auto [a, b] = dq.front();
auto [x,y]=b;
dq.pop_front();
if(a>d[x][y]) continue;
for (i64 i = 0; i < 4; i++) {
i64 nx = x + dx[i];
i64 ny = y + dy[i];
if (nx < 0 || nx >= n || ny < 0 || ny >= m)
continue;
i64 q = (go[x][y] != go[nx][ny]);
if (d[x][y] + q < d[nx][ny]) {
d[nx][ny] = d[x][y] + q;
if (q) {
dq.push_back({d[nx][ny],{nx, ny}});
} else {
dq.push_front({d[nx][ny],{nx, ny}});
}
}
}
}
cout << d[ex][ey] << endl;
}
return 0;
}
没有评论