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

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

   这样做的好处是什么?
      我们不妨假设每次搜索的分支因子是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函数 http://128kj.iteye.com/blog/1699795

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

你可能感兴趣的:(java,数据结构,编程,算法)