广度优先搜索学习五例之五

如果已经知道搜索的开始状态和结束状态,要找一个满足某种条件的一条路径(一般是最短路径),为了避免无谓的“组合爆炸”产生,就可以采取双向广度搜索算法,也就是从开始状态和结束状态同时开始搜索,一个向前搜,一个向后找。 直至在两个扩展方向上出现同一个子结点,搜索结束,这就是双向搜索过程。出现的这个同一子结点,我们称为相交点,如果确实存在一条从初始结点到目标结点的最佳路径,那么按双向搜索进行搜索必然会在某层出现“相交”,即有相交点,初始结点一相交点一目标结点所形成的一条路径即是所求路径。

这样做的好处是什么?
我们不妨假设每次搜索的分支因子是r,如果最短的路径长为L的话(也就是搜了L层),那么,用一般的BFS算法(不考虑去掉重复状态),总的搜索状态数是r^L(^表示乘方运算);而如果采取双向BFS算法,那么,从前往后搜,我们只需要搜索L/2层,从后往前搜,我们也只要搜L/2层,因此,搜索状态数是2*(r^(L/2)),比普通BFS就快了很多了。

双向BFS算法的实质还是BFS,只不过两边同时开始BFS而已。还是可以利用队列来实现:可以设置两个队列,一个用于向前的BFS,另一个用于向后的BFS,利用这两个队列,同时从前、后开始层次遍历搜索树。


结点扩展顺序
双向扩展结点,在两个方向的扩展顺序上,可以轮流交替进行,但由于大部分的解答树并不是棵完全树,在扩展完一层后,下一层则选择结点个数较少的那个方向先扩展,可以克服两个方向结点生成速度不平衡的状态,明显提高搜索效率。

双向广度优先算法编程的基本框架如下:

数据结构:
Queue q1, q2; //两个队列分别用于两个方向的扩展(注意在一般的广度优先算法中,只需要一个队列)

算法流程:
void DBFS(){
1. 将起始节点放入队列q1,将目的节点放入队列q2
2. 当 两个队列都未空时,作如下循环
1) 如果队列q1里的未处理节点比q2中的少,则扩展队列q1,并判断是否出现相交点;
2) 否则扩展队列q2,并判断是否出现相交点;
3. 如果队列q1未空,循环扩展q1直到为空
4. 如果队列q2未空,循环扩展q2直到为空
}

例:再解POJ 1077
题意:8数码问题,给出一个含数字1~8和字母x的3*3矩阵,如:

2 3 4
1 5 X
7 6 8

现在要你移动x的位置(上下左右),使得这个矩阵为:

1 2 3
4 5 6
7 8 x
求出移动步数最少的移动方案。

例如程序中输入:2 3 4 1 5 0 7 6 8(代表要从此状态转为后一状态123456780),则应该输出:ullddrurdllurdruldr(上左左下下右上右下左左上右下右上左下右)


思路:这里用双向bfs。状态可采用全排列的hash函数[url]http://128kj.iteye.com/blog/1699795[/url]

import java.util.*;
public class Main
{
private int[][] arr1,arr2;//保存中间过程的数组
private String [] bb1=new String[362882];//9!=362880,最大状态9!,标记访问过的结点,同时记录操作动作
private String [] bb2=new String[362882];
private Queue< my> qu1=new LinkedList< my>();//用于向前向后的两个队列
private Queue< my> qu2=new LinkedList< my>();
static final int fac[] = {1,2,6,24,120,720,5040,40320}; //用于计算全排列的Hash值

public Main(int[][] arr1,int[][] arr2){
this.arr1=arr1;
this.arr2=arr2;
}

public static void main(String[] args){
Scanner in=new Scanner(System.in);
int[][] arr1=new int[5][5];//起点
int[][] arr2={{0,0,0,0,0},//目标
{0,1,2,3,0},
{0,4,5,6,0},
{0,7,8,0,0},
{0,0,0,0,0}};
String s;
for(int i=1;i< 4;i++){
for(int j=1;j< 4;j++){
s=in.next();
if(s.equals("x"))arr1[i][j]=0;
else arr1[i][j]=Integer.parseInt(s);
}
}

Main m=new Main(arr1,arr2);
int from=m.getNum(arr1);//数组表示的状态转为用整数表示

int to=m.getNum(arr2);//数组表示的状态转为用整数表示

m.Dbfs(from,to);
}


private void Dbfs(int from,int to){
int ha=0;
qu1.offer(new my("",from)); //起点入队列1
qu2.offer(new my("",to)); //目标入队列2
while(!qu1.isEmpty()&&!qu2.isEmpty()){
if(!qu1.isEmpty()){//从起点向目标广度优先搜索搜索
my h=qu1.poll();
int u=h.u;
String s=h.s;
ha=hash(u,9);
if(bb2[ha]!=null){//双向搜索出现相交点,输出结果
System.out.println(s+bb2[ha]);
return;
}
if(bb1[ha]!=null) continue;//此状态已访问过
bb1[ha]=s;//记录操作动作,标记为已访问
int i=-1,j=-1,p=u;
//从整数表示的状态转为数据组表示状态,并找出“0”的位置,应该写成一个方法.
for(int u1=3;u1>0;u1--){
for(int u2=3;u2>0;u2--){
arr1[u1][u2]=p%10;
if(arr1[u1][u2]==0){
i=u1;
j=u2;
}
p/=10;
}
}

//从四个方向访问当前状态的邻接点
change(arr1,i,j,i-1,j);//向上一步
int y=getNum(arr1);
qu1.add(new my(s+"u",y));//邻接点入队
change(arr1,i-1,j,i,j);//复位

change(arr1,i,j,i+1,j);
y=getNum(arr1);
qu1.add(new my(s+"d",y));
change(arr1,i+1,j,i,j);

change(arr1,i,j,i,j+1);
y=getNum(arr1);
qu1.add(new my(s+"r",y));
change(arr1,i,j+1,i,j);

change(arr1,i,j,i,j-1);
y=getNum(arr1);
qu1.add(new my(s+"l",y));
change(arr1,i,j-1,i,j);
}


if(!qu2.isEmpty()){从目标向起点广度优先搜索搜索
my h=qu2.poll();
int u=h.u;
String s=h.s;
ha=hash(u,9);
if(bb1[ha]!=null){//双向搜索出现相交点,输出结果
System.out.println(bb1[ha]+s);
return;
}

if(bb2[ha]!=null) continue;
bb2[ha]=s;
int i=-1,j=-1,p=u;
for(int u1=3;u1>0;u1--){
for(int u2=3;u2>0;u2--){
arr2[u1][u2]=p%10;
if(arr2[u1][u2]==0){
i=u1;
j=u2;
}
p/=10;
}
}
change(arr2,i,j,i,j+1);
int y=getNum(arr2);
qu2.add(new my("l"+s,y));
change(arr2,i,j+1,i,j);

change(arr2,i,j,i,j-1);
y=getNum(arr2);
qu2.add(new my("r"+s,y));
change(arr2,i,j-1,i,j);

change(arr2,i,j,i-1,j);
y=getNum(arr2);
qu2.add(new my("d"+s,y));
change(arr2,i-1,j,i,j);

change(arr2,i,j,i+1,j);
y=getNum(arr2);
qu2.add(new my("u"+s,y));
change(arr2,i+1,j,i,j);
}
}
System.out.println("unsolvable");
}

private int hash(int num,int k){//全排列的哈西函数
int n[]=new int[k];
for(int i = k-1; i >=0; i--){
n[i] = num % 10;
num /= 10;
}

int key = 0;
int c;
for(int i = 1; i <k; i++){
c=0;
for(int j = 0; j < i; j++)
if(n[j] > n[i]) c++;
key += c * fac[i-1];
}
return key;
}

private int getNum(int[][] arr){
int t=0;
for(int i=1;i< 4;i++)
for(int j=1;j< 4;j++){
t*=10;
t+=arr[i][j];
}
return t;
}

private void change(int[][] arr,int x1,int y1,int x2,int y2){
arr[x1][y1]=arr[x2][y2];
arr[x2][y2]=0;
}
}
class my{
String s="";
int u;
public my(String s,int u){
this.s=s;
this.u=u;
}
}

运行:
D:\java>java Main
2 3 4 1 5 0 7 6 8
ullddrurdllurdruldr
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值