【算法】BFS+哈希解决八数码问题

15拼图已经有超过100年; 即使你不叫这个名字知道的话,你已经看到了。它被构造成具有15滑动砖,每一个从1到15上,并且所有包装成4乘4帧与一个瓦块丢失。让我们把丢失的瓷砖“X”; 拼图的目的是安排瓷砖以便它们排序为:


1 2 3 4
5 6 7 8
9 10 11 12
13 14 15×


这里唯一合法经营是交流'X'与它共享一个边缘的瓷砖之一。作为一个例子,举动下列顺序解决了一个稍微加扰难题:


1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4
5 6 7 8 5 6 7 8 5 6 7 8 5 6 7 8
9×10 12 9 10×12 9 10 11 12 9 10 11 12
13 14 11 15 13 14 11 15 13 14×15 13 14 15×
R-> D-> R->


上一行中的字母指示哪个的“x”瓦片的邻居交换在每一步的“x”瓦片; 合法的值是'R','L','U'和'D',右,左,上,下,分别。

并非所有的难题都可以解决; 在1870年,一个叫萨姆·劳埃德的人是著名的分配难题的一个无法解决的版本,
折腾了不少人。事实上,所有你必须做的,使一个普通的益智成无法解决的一个是交换两个瓷砖(不包括课程的缺失'X'瓦)。

在这个问题中,你会写三个解决不太知名的8益智,砖组成一个三的程序
安排。


 

思路分析:

从第一步开始,我们每一步都有不多于四种选择:将白格向上移;向下移;向左移;向右移。这很像是做迷宫。走迷宫的任务是走到某一点就算赢。而八数码,是走到什么局面才算赢。那么队列中存放的就不是点,而是面。换句话说,就是图。现在问题变得很简单,只要你能在一张3×3的图上裸着BFS一遍,搜到目标图就算你赢了。就像迷宫的vis数组一样,我们需要对走过的局面设置标记,不要重复走。

 

在做这道题中学到的几样小技巧:

 

1. 数组直接用memcpy, memcmp对整块内存进行复制或者比较, 速度比用for循环快。

 

2.用typedef来定义一个新名称可以更加方便。

 

3.哈希表与编码的应用

 

    #include<stdio.h>
    #include<string.h> 
    #define MAXN 500000  
  
    char input[30];  
    int state[9], goal[9] = {1,2,3,4,5,6,7,8,0};  
    int dir[4][2] = {{-1,0},{1,0},{0,-1},{0,1}}; // 上,下,左, 右  
    char path_dir[5] = "udlr";  
    int st[MAXN][9];  
    int father[MAXN], path[MAXN]; // 保存打印路径  
      
    const int MAXHASHSIZE = 1000003;  
    int head[MAXHASHSIZE], next[MAXN];  
      
    void init_lookup_table() { memset(head, 0, sizeof(head)); }  
      
    typedef int State[9];  
    int hash(State& s) {  
      int v = 0;  
      for(int i = 0; i < 9; i++) v = v * 10 + s[i];  
      return v % MAXHASHSIZE;  
      
    }  
      
    int try_to_insert(int s) {  
      int h = hash(st[s]);  
      int u = head[h];  
      while(u) {  
        if(memcmp(st[u], st[s], sizeof(st[s])) == 0) return 0;  
        u = next[u];  
      }  
      next[s] = head[h];  
      head[h] = s;  
      return 1;  
    }  
      
    int bfs(){  
        init_lookup_table();  
        father[0] = path[0] = -1;  
        int front=0, rear=1;  
        memcpy(st[0], state, sizeof(state));  
          
        while(front < rear){  
            int *s = st[front];  
             
            if(memcmp(s, goal, sizeof(goal))==0){  
                return front;  
            }  
      
            int j;  
            for(j=0; j<9; ++j) if(!s[j])break; // 找出0的位置  
            int x=j/3, y=j%3;     // 转换成行,列  
              
            for(int i=0; i<4; ++i){  
      
                int dx = x+dir[i][0]; // 新状态的行,列  
                int dy = y+dir[i][1];  
                int pos = dx*3+dy;    // 目标的位置  
      
                if(dx>=0 && dx<3 && dy>=0 && dy<3){  
                    int *newState = st[rear];  
                    memcpy(newState, s, sizeof(int)*9);  
                    newState[j] = s[pos];  
                    newState[pos] = 0;  
                    if(try_to_insert(rear)){  
                        father[rear] = front;  path[rear] = i;  
                        rear++;  
                    }  
                }  
            }   
            front++;  
        }  
        return -1;  
    }  
      
    void print_path(int cur){  
        if(cur!=0){  
            print_path(father[cur]);  
            printf("%c", path_dir[path[cur]]);  
        }  
    }  
      
    int main(){  
          
        while(gets(input)){  
            // 转换成状态数组, 'x'用0代替  
            for(int pos=0, i=0; i<strlen(input); ++i){  
                if(input[i]>='0' && input[i]<='9')  
                    state[pos++] = input[i]-'0';  
                else if(input[i]=='x')  
                    state[pos++] = 0;  
            }  
            int ans;  
            if((ans=bfs())!=-1){   
                print_path(ans);  
                printf("\n");  
            }  
        }          
    }  

 

转载于:https://www.cnblogs.com/KID-XiaoYuan/p/6412837.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值