zoukankan      html  css  js  c++  java
  • Poj 1077 eight(BFS+全序列Hash解八数码问题)

    一、题意

          经典的八数码问题,有人说不做此题人生不完整,哈哈。给出一个含数字1~8和字母x的3 * 3矩阵,如:

                X
               3 4  6
                8

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

               
                6
                x

         求出最后能得到这个解的移动方案,输出移动的操作。(不要求最优解,也就是不要求移动次数最少)

    二、题解

          这个8数码问题,我们可以把它看成是一个全排列,从一个初始的排列通过移动元素的位置,到达最终的123456789的这种排列,这里的X我们用9代替。

          我们知道X有四个选择移动的方向,但是不一定有四个,如果在左上角就只能右移和下移了,还有很多种限制情况。由于数组是从0开始的,所以他的下标应该是:

          0  1  2

          3  4  5

          6  7  8

         if( id % 3 != 2)如果id不是2,5,8就可以向右移动;
         if (id % 3 != 0) 如果id不是0,3,6就可以向左移动;
         if( id > 2 ) 如果id大于2就可以向上移动;
         if( id < 6)  如果id小于6就可以向下移动。
         现在我们有了移动的方向了,但是我们知道可能这些选择中包含着以前就走过的状态。那怎么办来避免已经访问过的状态呢,这就用到了把全排列转换成数字的hash函数(这里其实还可以用康托展开),每一个排列都能对应一个数字,如果这个数字出现过就不访问。这可以用一个Boolean数组实现。

        这个hash函数的原理用到了变进制和序数对的知识,详细信息请参考:http://blog.sina.com.cn/s/blog_6635898a0100p4re.html

        接下来就要用到BFS来找到状态转换路径了,每一个状态用一个类表示,包括前一个状态到本状态的操作本状态的数本状态X所处位置索引前一个状态的hash值

        遍历每个状态从0开始,记录可以到达的状态(一次最多可以到达四个),直到num=123456789。例如,0可以到1、2、3,而1可以到4、5,2可以到6、7,...并用

        q[tail].pre = head记录之前的的结点,这样到最后的最终结点往前遍历就可以了。q[tail].op = op记录了操作,输出操作就可以了。这就相当于找到了一条路径,路径上的结点记录了到这一步的操作,输出操作就行了。但想之前所说的,这不一定是最优解,这是最快到达的,不一定是操作最少的。

    三、java代码

    import java.util.Scanner;
    
    class Status{
    	char operation;
    	int number, index, previous;
    }
    
    public class Main{
    	static int Max = 363000;  //总得状态数量, 9!=362880
    	static Status[] q=new Status[Max];
    	static int head;
    	static int tail;
    	static int factorial[] = {1,1,2,6,24,120,720,5040,40320};
    	static int Pow[] = {100000000,10000000,1000000,100000,10000,1000,100,10,1};
    	static boolean[] vis=new boolean[Max];
    	// 全排列的hash函数。
    	static int permutationToNumberHash(int num){ 
    	    int i, j;
    	    int[] n=new int[10];
    	    for(i = 0; i < 9; i ++){
    	        n[i] = num % 10;
    	        num /= 10;
    	    }
    	    int c, key = 0;
    	    for(i = 1; i < 9; i ++){
    	        for(c = 0, j = 0; j < i; j ++)
    	            if(n[j] < n[i]) 
    	            	c ++;
    	        key += c * factorial[i];
    	    }
    	    return key;
    	}
    	
    	static void exchangeLocation(int num, int a, int b, char op){ // 操作:第a个数和第b个数交换。
    	    int n1, n2;
    	    n1 = num / Pow[a] % 10;
    	    n2 = num / Pow[b] % 10;
    	    num = num - (n1-n2)*Pow[a] + (n1-n2)*Pow[b]; //移动后的数字大小
    	    int key = permutationToNumberHash(num);
    	    if(!vis[key]){
    	        vis[key] = true;
    	        q[tail].operation = op;
    	        q[tail].number = num;
    	        q[tail].previous = head;
    	        q[tail ++].index = b;
    	    }
    	}
    
    	static void output(int k){
    	    if(q[k].operation != 0){
    	       output(q[k].previous);
    	       System.out.print( q[k].operation);
    	    }
    	}
      public static void main(String args[]){
    	 Scanner sc=new Scanner(System.in);
    	    int i, num, id , t;
    	    char c;
    	    id=-1;
    	    //读入数据,用9代替x,用一个十进制数表示读入数据。
    	    for(num = i = 0; i < 9; i ++){
    	        c=sc.next().charAt(0);
    	        if(c == 'x'){
    	            t = 9;
    	            id = i;
    	        }else 
    	        	t = c - '0';
    	        num = 10 * num + t;//顺序存储
    	    }
    	    //初始化每一个状态
    	    for(i=0;i<Max;i++){
    	    	q[i]=new Status();
    	    }
    	    boolean flag = false;
    	    head = 0;
    	    tail = 1;
    	    //q[0]表示x所在的的id和状态数
    	    q[0].index = id;
    	    q[0].number = num;
    	    //广度搜索找出一条路径,最后的数是123456789.
    	    /*具体实现:
    	     * 遍历每个状态从0开始,记录可以到达的状态(一次最多可以到达四个)。直到num=123456789。
    	     * 例如,0可以到1、2、3,而1可以到4、5,2可以到6、7,...并用 q[tail].pre = head记录之前的的结点,这样到最后
    	     * 的最终结点往前遍历就可以了。q[tail].op = op记录了操作,输出操作就可以了。
    	     * */
    	    while(tail > head && !flag){
    	        int cnt = tail - head;
    	        while(cnt --!=0){
    	            num = q[head].number;
    	            if(num == 123456789){
    	                flag = true; 
    	                break;
    	            }
    	            id = q[head].index;
    	            //注意这里的移动方向是指数的移动方向,不是X的移动方向
    	            //还有id的范围是0~8
    	            /* 0 1 2
    	             * 3 4 5
    	             * 6 7 8
    	             */
    	            if(id % 3 != 2)   //如果id不是2,5,8就可以向右移动
    	            	exchangeLocation(num, id, id + 1, 'r');
    	            if(id % 3 != 0)   //如果id不是0,3,6就可以向左移动
    	            	exchangeLocation(num, id, id - 1, 'l');
    	            if(id > 2)        //如果id大于2就可以向上移动
    	            	exchangeLocation(num, id, id - 3, 'u');
    	            if(id < 6) 	      //如果id小于6就可以向下移动
    	            	exchangeLocation(num, id, id + 3, 'd');
    	            head ++;
    	        }
    	    }
    	    if(flag) 
    	    	output(head);
    	    else 
    	    	System.out.println("unsolvable");
      } 
    }
    
    
    参考:http://blog.sina.com.cn/s/blog_6635898a0100p4sx.html

    版权声明:本文为博主原创文章,未经博主允许不得转载。

  • 相关阅读:
    第七章 路由 68 路由-前端路由和后端路由的概念
    第六章 组件 67 使用ref获取DOM元素和组件引用
    第六章 组件 65-66 组件案例-发表评论功能的实现
    第六章 组件 63 组件传值-父组件向子组件传值和data与props的区别
    第六章 组件 62 组件-组件定义方式的复习
    第六章 组件 61 动画-小球动画flag标识符的作用分析
    第六章 组件 60 组件切换-应用切换动画和mode方式
    第六章 组件 59 组件切换-使用Vue提供的component元素实现组件切换
    vscode代码格式化 空格的配置
    vim配置C++开发环境 win10
  • 原文地址:https://www.cnblogs.com/AndyDai/p/4734124.html
Copyright © 2011-2022 走看看