这是我参与「第四届青训营 」笔记创作活动的第3天
双向广搜简介
当搜索有确定的起点 s 和终点 t,并且能把从起点到终点的单向搜索,变换为分别从起点出发和从终点出发的“相遇”问题。此时可进行如下优化:从起点 s(正向)和终点 t(逆向)同时开始搜索,当两个搜索产生相同的一个子状态 v 时就结束。得到的 s-v-t是一条最佳路径。不过要注意,和普通 BFS 一样,双向广搜在搜索时并没有“方向感”,所谓“正向搜索”和“逆向搜索”其实也是盲目的,它们从 s 和 t 逐层扩散出去,直到相遇为止。
切入口(例题)
如下图所示: 有 9 只盘子,排成 1 个圆圈。 其中 8 只盘子内装着 8 只蚱蜢,有一个是空盘。 我们把这些蚱蜢顺时针编号为 1 ~ 8。
每只蚱蜢都可以跳到相邻的空盘中, 也可以再用点力,越过一个相邻的蚱蜢跳到空盘中。
请你计算一下,如果要使得蚱蜢们的队形改为按照逆时针排列, 并且保持空盘的位置不变(也就是 1−8 换位,2−7换位,...),至少要经过多少次跳跃?
C++代码
#include<bits/stdc++.h> using namespace std;
bool meet= false; int cnt = 0; //统计算了多少次
void extend(queue<string> &q,map<string,int> &m1,map<string,int> &m2){
string s = q.front();
q.pop();
int i;
for(i=0;i<(int)s.size();i++) //找到’0‘的位置
if(s[i]=='0') break;
for(int j=0;j<4;j++){ //跳到4个位置
cnt++;
string news = s;
if(j==0) swap(news[(i-2+9)%9],news[i]);
if(j==1) swap(news[(i-1+9)%9],news[i]);
if(j==2) swap(news[(i+1+9)%9],news[i]);
if(j==3) swap(news[(i+2+9)%9],news[i]);
if(m2[news]){ //正向搜索和逆向搜索相遇了:这个状态在m2中出现过
cout << m1[s] + 1 + m2[news] << endl;
//输出步数,然后退出结束 cout << cnt << endl;
//计算了 54568次
meet = true;
return;
}
if(!m1[news]){
//判重:这个状态没有出现过,放进队列
q.push(news); m1[news] = m1[s]+1;
//记录步数
} }
meet = false; //没有相遇
}
void bfs(){ string start = "012345678"; //起点状态
string ed = "087654321"; //终点状态
queue<string> q1, q2; //正向搜索队列、逆向搜索队列
q1.push(start);
q2.push(ed);
map<string,int> mp1, mp2; //用map判重
mp1[start]=0;
mp2[ed]=0;
while(q1.size() && q2.size()){
if(q1.size()<=q2.size()) //如果正向BFS队列小,先扩展它
extend(q1,mp1,mp2); //扩展时,判断是否相遇
else //否则扩展逆向BFS
extend(q2,mp2,mp1);
if(meet) break; }
}
int main(void){
bfs();
return 0;
}
JAVA代码
import java.util.Scanner;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Arrays;
public class Main {
static boolean meet= false;
static int cnt = 0;
static ArrayDeque<String> q = new ArrayDeque<String>();
static HashMap<String, Integer> m1 = new HashMap<String, Integer>();
static HashMap<String, Integer> m2 = new HashMap<String, Integer>();
public static String swap(String s,int a,int b) {
StringBuilder str=new StringBuilder(s);
char l=s.charAt(a),r=s.charAt(b);
str.setCharAt(a, r);
str.setCharAt(b, l);
return str.toString();
}
public static void extend(ArrayDeque<String> q,HashMap<String, Integer> m1,HashMap<String, Integer> m2) {
String s = q.getLast();
q.remove(s);
int i;
for(i=0;i<s.length();i++) //找到’0‘的位置
if(s.charAt(i)=='0')
break;
//System.out.println(i);
for(int j=0;j<4;j++){ //跳到4个位置
cnt++;
String news = s;
if(j==0) news=swap(news,((i-2+9)%9),(i));
if(j==1) news=swap(news,((i-1+9)%9),(i));
if(j==2) news=swap(news,((i+1+9)%9),(i));
if(j==3) news=swap(news,((i+2+9)%9),(i));
//System.out.println(news);
if(m2.containsKey(news)){ //正向搜索和逆向搜索相遇了:这个状态在m2中出现过
//System.out.println(m1.get(s) +" "+ m2.get(news));
System.out.println(m1.get(s) + 1 + m2.get(news));
System.out.println(cnt);
meet = true;
return;
}
if(!m1.containsKey(news)){ //判重:这个状态没有出现过,放进队列
// System.out.println(news);
q.push(news);
int k= m1.get(s)+1; //记录步数
m1.put(news, k);
}
}
meet = false; //没有相遇
}
public static void bfs(){
String start = "012345678"; //起点状态
String ed = "087654321"; //终点状态
// queue<string> q1, q2; //正向搜索队列、逆向搜索队列
ArrayDeque<String> q1 = new ArrayDeque<String>();
ArrayDeque<String> q2 = new ArrayDeque<String>();
q1.push(start); q2.push(ed);
// map<string,int> mp1, mp2; //用map判重
HashMap<String, Integer> mp1 = new HashMap<String, Integer>();
HashMap<String, Integer> mp2 = new HashMap<String, Integer>();
mp1.put(start, 0); mp2.put(ed, 0);
while(q1.size()*q2.size()!=0){ // System.out.println(ed);
// System.out.println(q1.size());System.out.println(q2.size());
if(q1.size()<=q2.size()) //如果正向BFS队列小,先扩展它
extend(q1,mp1,mp2); //扩展时,判断是否相遇
else //否则扩展逆向BFS
extend(q2,mp2,mp1);
if(meet) break;
}
}
public static void main(String[] args) {
bfs();
}}
注:第二个输出cnt=54560相较于C++代码的54568少8,是因为JAVA中利用键值对的判重采用
.containsKey(news)
对应C++if(!m1[news])
初始为0。因这个变量用于统计计算次数,比较算法优劣,故不做更改。
浅析CPP和爪哇的区别
- C++的string和JAVA的String
- C++中队列
queue<string> q;
;
JAVA中队列ArrayDeque<String> q = new ArrayDeque<String>();
- C++中键值对
map<string,int> mp;
;
JAVA中键值对HashMap<String, Integer> mp = new HashMap<String, Integer>();
资料参考:蓝桥云课《蓝桥杯软件类高效备赛班》