双向广度优先遍历

算法学习笔记

Posted by Nicodechal on 2020-01-14

本文说明双向广度优先遍历算法,并以 LeetCode.127 说明双向广度优先遍历的使用方式。

常规的广度优先遍历

常规的广度优先遍历通常会使用队列实现,首先将起点加入队列,然后不停遍历队列直到队列为空或找到结果,每次取出队头进行处理,处理完毕后,如果还未得到结果,则将和队头相关连的未被访问过的节点加入到队列中。

例如,求一个节点到另一个节点的最短路径的代码可能如下 (不是完整代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function bfs(g) {
// q uses to save nodes in current level.
let q = [g.start], level = 0;
while (q.length) {
const nq = [];
for (const val of q) {
if (val == g.end) return level;
for (const next of val.children) {
nq.push(next);
}
}
q = nq;
level++;
}
return 0;
}

双向广度优先遍历

考虑之前需要解决的问题,由于我们是要找一个节点到另一个节点的最短距离,因此,除了从头节点开始查找,也可以从尾部节点开始查找,所以,双向广度优先遍历同时从两端开始进行 BFS,交替选择继续从头部遍历或从尾部遍历,每次选择层中此时节点较少的一端遍历,加快搜索速度,由于使用 BFS 查找最短路径,因此会遍历起点所在层到终点所在层的节点,每次选择节点少的一端遍历使得每一层需要遍历的节点变少,因此可以加快搜索速度。时间复杂度和普通 BFS 相同,但是有常数的优化。这个方法使用 LeetCode.127 举例说明。

一个例子

问题设定一个单词可以转移到一个和他相比只变换了一个字符的单词,问从起点单词开始,最少需要几步到达终点单词。

这里考虑使用双端 BFS,代码和解释如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
var ladderLength = function(bw, ew, wl) {
wl = new Set(wl);
if (!wl.has(ew)) return 0;
// start set and end set.
let ss = new Set([bw]), es = new Set([ew]);

let level = 1;
while (ss.size) {
// new start set
const nss = new Set();
// each word in ss
for (const k of ss) {
// find if the word changes a char can get an exist word.
// iterate through different index and different replace char.
for (let i = 0; i < k.length; i++) {
for (let j = 0; j < 26; j++) {
const ch = String.fromCharCode(97 + j);
if (ch != k[i]) {
// generate new string.
const ns = k.slice(0, i) + ch + k.slice(i + 1);
// find the end, return.
if (es.has(ns)) {
return level + 1;
}
// the next word exist, add to new start set.
if (wl.has(ns)) {
nss.add(ns);
wl.delete(ns);
}
}
}
}
}
level++;
ss = nss;
// select smaller set as start set. **!important**
if (ss.size > es.size) {
[ss, es] = [es, ss];
}
}
return 0;
};

小结

本文说明了双向 BFS 及其应用,当遍历的目标和起点均已知时,可以使用该方法提高效率。