BFS-双向广搜| 青训营笔记

118 阅读2分钟

这是我参与「第四届青训营 」笔记创作活动的第3天

双向广搜简介

当搜索有确定的起点 s 和终点 t,并且能把从起点到终点的单向搜索,变换为分别从起点出发和从终点出发的“相遇”问题。此时可进行如下优化:从起点 s(正向)和终点 t(逆向)同时开始搜索,当两个搜索产生相同的一个子状态 v 时就结束。得到的 s-v-t是一条最佳路径。不过要注意,和普通 BFS 一样,双向广搜在搜索时并没有“方向感”,所谓“正向搜索”和“逆向搜索”其实也是盲目的,它们从 s 和 t 逐层扩散出去,直到相遇为止。

切入口(例题)

如下图所示: 有 9 只盘子,排成 1 个圆圈。 其中 8 只盘子内装着 8 只蚱蜢,有一个是空盘。 我们把这些蚱蜢顺时针编号为 1 ~ 8。 image.png
每只蚱蜢都可以跳到相邻的空盘中, 也可以再用点力,越过一个相邻的蚱蜢跳到空盘中。

请你计算一下,如果要使得蚱蜢们的队形改为按照逆时针排列, 并且保持空盘的位置不变(也就是 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; 
}

image.png

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();
                         }}

image.png 注:第二个输出cnt=54560相较于C++代码的54568少8,是因为JAVA中利用键值对的判重采用.containsKey(news)对应C++if(!m1[news])初始为0。因这个变量用于统计计算次数,比较算法优劣,故不做更改。

浅析CPP和爪哇的区别

  1. C++的string和JAVA的String
  2. C++中队列queue<string> q;
    JAVA中队列ArrayDeque<String> q = new ArrayDeque<String>();
  3. C++中键值对map<string,int> mp;
    JAVA中键值对HashMap<String, Integer> mp = new HashMap<String, Integer>();

资料参考:蓝桥云课《蓝桥杯软件类高效备赛班》