无聊打发时间的时候就学了学一些LeetCode的题,以后也可以说知道这个东西啦
双向BFS
当用BFS算法搜索可行解的时候,可能会在深层子数上花费较多的时间,比如多叉树随着层数的增长,子树节点个数将以指数级别增长。
如果已经搜索的初态和终态,可以从初态和终态分别同时进行BFS搜索
左图是传统BFS搜索的空间状态,右图是双向搜索的空间状态
当采用传统BFS搜索到可行解的时候已经搜索了非常多的子树;而采用双向BFS搜索到可行解的时候相比于前者少了很多搜索空间
入门例题
如题,由于DFS搜索的深度未知,不太适合用普通的DFS(可能stackoverflow);因为可以同时知道搜索的初态(字符串A)和搜索的终态(字符串B)。可以采用双向BFS进行优化BFS。
代码比较简单,主要就是用两个队列模拟A -> B正向BFS搜索以及B-> A反向BFS搜索的过程,当变换后的A’ == B’ ,说明二者相遇,输出步数之和即可。
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
String[] sp = scan.nextLine().split(" ");
// 读入字符串A和B
String a = sp[0], b = sp[1];
String[] ra = new String[6], rb = new String[6];
int len = 0;
while (scan.hasNextLine()) {
sp = scan.nextLine().split(" ");
// 依次读入转换规格ra[]和rb[]
ra[len] = sp[0];
rb[len++] = sp[1];
}
// 特别判断一下,如果一开始就相等,就不用再搜索,否则会搜索不到结果
if (a.equals(b)) {
System.out.println(0);
return;
}
int cnta = 0, cntb = 0;
// seta和setb标记已经qa和qb两个队列搜索过的节点
HashSet<String> seta = new HashSet<>(), setb = new HashSet<>();
seta.add(a);
setb.add(b);
// 设置两个BFS搜索的队列
Deque<String> qa = new ArrayDeque<>(), qb = new ArrayDeque<>();
qa.addLast(a);
qb.addLast(b);
while (!qa.isEmpty() && !qb.isEmpty()) {
// 每次优先遍历少的队列
if (qa.size() < qb.size()) {
// 正向搜索
int sa = qa.size();
for (int tt = 0; tt < sa; tt++) {
String top = qa.removeFirst();
// 对于字符串top,遍历所有的转换规则
for (int i = 0; i < len; i++) {
for (int j = 0; j < top.length(); j++) {
int k = 0, jj = j;
// 对于每种规则,判断该规则是否是top的字串,是的话替换生成新的子节点
for (k = 0; k < ra[i].length() && jj < top.length(); k++, jj++) {
if (ra[i].charAt(k) != top.charAt(jj)) break;
}
if (k == ra[i].length() && ra[i].length() <= top.length()) {
// 按照规则替换后的子节点串ne
String ne = top.substring(0, j) + rb[i]
+ top.substring(j + ra[i].length(), top.length());
// 如果qa队列中已经遍历过ne了,跳过ne
if (seta.contains(ne)) continue;
// 如果ne在qa(另一个搜索方向)中已经遍历过了,判断两个方向的步数是不是满足条件
if (setb.contains(ne)) {
if (cnta + cntb + 1 <= 10) {
System.out.println(cnta + cntb + 1);
return;
} else {
continue;
}
}
seta.add(ne);
qa.addLast(ne);
}
}
}
}
// 正向搜索的步数+1
cnta++;
} else {
// 反向搜索
// qb的遍历过程和qa的一样,可以抽取成一个函数,为了展示就这样了
int sb = qb.size();
for (int tt = 0; tt < sb; tt++) {
String top = qb.removeFirst();
for (int i = 0; i < len; i++) {
for (int j = 0; j < top.length(); j++) {
int k = 0, jj = j;
for (k = 0; k < rb[i].length() && jj < top.length(); k++, jj++) {
if (rb[i].charAt(k) != top.charAt(jj)) break;
}
if (k == rb[i].length() && rb[i].length() <= top.length()) {
String ne = top.substring(0, j) + ra[i]
+ top.substring(j + rb[i].length(), top.length());
if (setb.contains(ne)) continue;
if (seta.contains(ne)) {
if (cnta + cntb + 1 <= 10) {
System.out.println(cnta + cntb + 1);
return;
} else {
continue;
}
}
setb.add(ne);
qb.addLast(ne);
}
}
}
}
cntb++;
}
}
System.out.println("NO ANSWER!");
}
}
简单分析
这题采用BFS,假设每个节点有k个子节点,那么,step次搜索步长,大概将会有k ^ step个搜索空间;采用双向BFS,由于正向搜索一半的高度(步长一半),搜索空间为k ^ (step / 2),所以双向搜索的空间为2 * k * (step / 2)。
假设,k = 10, step = 8
BFS:10 ^ 8 = 1, 0000, 0000
双向BFS:2 * 10 ^ (8 / 2) = 2, 0000
两者相差了将近 5000倍