zoukankan      html  css  js  c++  java
  • HDU 1043 POJ 1077 八数码问题

    以下内容转载自:http://www.cnblogs.com/goodness/archive/2010/05/04/1727141.html

    八数码的八境界

      研究经典问题,空说不好,我们拿出一个实际的题目来演绎。八数码问题在北大在线测评系统中有一个对应的题,题目描述如下:

    Eight

    Time Limit: 1000MS    Memory Limit: 65536K  Special Judge

    Description                                 

    The 15-puzzle has been aroundfor over 100 years; even if you don't know it by that name, you've seen it. Itis constructed with 15 sliding tiles, each with a number from 1 to 15 on it,and all packed into a 4 by 4 frame with one tile missing. Let's call themissing tile 'x'; the object of the puzzle is to arrange the tiles so that theyare ordered as:

     1 2  3  4

     5 6  7  8

     9  10 1112

     13 14 15 x

    where the only legal operationis to exchange 'x' with one of the tiles with which it shares an edge. As anexample, the following sequence of moves solves a slightly scrambled puzzle:

     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  x 1012    9 10  x 12   9 10 11 12    9 10 11 12

    13 14 11 15   13 14 11 15  13 14  x 15   13 14 15 x

               r->           d->           r->

    The letters in the previousrow indicate which neighbor of the 'x' tile is swapped with the 'x' tile ateach step; legal values are 'r','l','u' and 'd', for right, left, up, and down,respectively.

    Not all puzzles can be solved;in 1870, a man named Sam Loyd was famous for distributing an unsolvable versionof the puzzle, and

    frustrating many people. Infact, all you have to do to make a regular puzzle into an unsolvable one is toswap two tiles (not counting the missing 'x' tile, of course).

    In this problem, you willwrite a program for solving the less well-known 8-puzzle, composed of tiles ona three by three arrangement.

    Input

    You will receive a descriptionof a configuration of the 8 puzzle. The description is just a list of the tilesin their initial positions, with the rows listed from top to bottom, and thetiles listed from left to right within a row, where the tiles are representedby numbers 1 to 8, plus 'x'. For example, this puzzle

     1 2  3

     x 4  6

     7 5  8

    is described by this list:

     1 2 3 x 4 6 7 5 8

    Output

    You will print to standardoutput either the word ``unsolvable'', if the puzzle has no solution, or astring consisting entirely of the letters 'r', 'l', 'u' and 'd' that describesa series of moves that produce a solution. The string should include no spacesand start at the beginning of the line.

    Sample Input

     2 3  4  1 5  x  7 6  8

    Sample Output

    ullddrurdllurdruldr

      这个题目是SpecialJudge,任意找出一组移法就行,但是很多时候,我们需要找到步数最少的移法,所以,这里以步数最少的移法为目的。 真正优化这个问题涉及到很多,比如A*、全排列哈希、堆优化等。一境界一代码,咱们一个境界一个境界走,慢慢优化这个经典问题,当然,我不是那么无聊,不 会把所有境界都列出代码……

      境界一、 暴力广搜+STL

      开始的时候,自然考虑用最直观的广搜,因为状态最多不超过40万,计算机还是可以接受的,由于广搜需要记录状态,并且需要判重,所以可以每次图 的状态转换为一个字符串,然后存储在stl中的容器set中,通过set的特殊功能进行判重,由于set的内部实现是红黑树,每次插入或者查找的复杂度为 Log(n),所以,如果整个算法遍历了所有状态,所需要的复杂度为n*Log(n),在百万左右,可以被计算机接受,由于对string操作比较费时, 加上stl全面性导致 速度不够快,所以计算比较费时,这样的代码只能保证在10秒内解决任何问题。但,明显效率不够高。POJ上要求是1秒,无法通过。

      境界二、广搜+哈希

      考虑到费时主要在STL,对于大规模的遍历,用到了ST的set和string,在效率上的损失是很大的,因此,现在面临一个严重的问题,必须 自己判重,为了效率,自然是自己做hash。有点麻烦,hash函数不好想,实际上是9!种排列,需要每种排列对应一个数字。网上搜索,得知了排列和数字 的对应关系。取n!为基数,状态第n位的逆序值为哈希值第n位数。对于空格,取其为9,再乘以8!。例 如,1 3 7 24 6 9 5 8 的哈希值等于:0*0! + 2*1! + 0*2! + 1*3! + 3*4! +1*5! + 0*6! + 1*7! + 0*8! <9!具体的原因可以去查查一些数学书,其中1 2 34 5 6 7 8 9 的哈希值是0 最小,9 8 7 6 54 3 2 1 的哈希值是(9!-1)最大。而其他值都在0 到(9!-1) 中,且均唯一。然后去掉一切STL之后,甚至包括String之后,得到单向广搜+Hash的代码,算法已经可以在三秒钟解决问题,可是还是不够 快!POJ时限是1秒,后来做了简单的更改,将路径记录方法由字符串改为单个字符,并记录父节点,得到解,这次提交,266ms是解决单问题的上限。当 然,还有一个修改的小技巧,就是逆序对数不会改变,通过这个,可以直接判断某输入是否有可行解。由于对于单组最坏情况的输入,此种优化不会起作用,所以不 会减少单组输入的时间上限。

      境界三、广搜+哈希+打表

      好,问题可以在200—300ms间解决,可是,这里我们注 意到一个问题,最坏情况下,可能搜索了所有可达状态,都无法找到解。如果这个题目有多次输入的话,每次都需要大量的计算。其实,这里可以从反方向考虑下, 从最终需要的状态,比如是POJ 1077需要的那种情况,反着走,可达的情况是固定的。可以用上面说的那种相应的Hash的方法,找到所有可达状态对应的值,用一个bool型的表,将可 达状态的相应值打表记录,用“境界三”相似的方法记录路径,打入表中。然后,一次打表结束后,每次输入,直接调用结果!这样,无论输入多少种情况,一次成 功,后面在O(1)的时间中就能得到结果!这样,对于ZOJ的多组输入,有致命的帮助!

      境界四、双向广搜+哈希
      Hash,不再赘述,现在,我们来进行进一步的优化,为了减少状态的膨 胀,自然而然的想到了双向广搜,从输入状态点和目标状态1 2 3 4 5 6 7 8 9同时开始搜索,当某方向遇到另一个方向搜索过的状态的时候,则搜索成功,两个方向对接,得到最后结果,如果某方向遍历彻底,仍然没有碰上另一方向,则无 法完成。

      境界五、A*+哈希+简单估价函数
      用到广搜,就可以想到能用经典的A*解决,用深度作为 g(n),剩下的自然是启发函数了。对于八数码,启发函数可以用两种状态不同数字的数目。接下来就是A*的套路,A*的具体思想不再赘述,因为人工智能课 本肯定比我讲的清楚。但是必须得注意到,A*需要满足两个条件:

    1.h(n)>h'(n),h'(n)为从当前节点到目标点的实际的最优代价值。

    2.每次扩展的节点的f值大于等于父节点的f值小。

    自然,我们得验证下我们的启发函数,h验证比较简单不用说,由于g是深度,每次都会较父节点增1。再看h,认识上, 我们只需要将h看成真正的“八数码”,将空格看空。这里,就会发现,每移动一次,最多使得一个数字回归,或者说不在位减一个。 h最多减小1,而g认为是深度,每次会增加1。所以,f=g+h, 自然非递减,这样,满足了A*的两个条件,可以用A*了!

      境界六、A*+哈希+曼哈顿距离

      A*的核心在启发函数上,境界五若想再提升,先想到的是启发函数。这里,曼哈顿距离可以用来作为我们的启发函数。曼哈顿距离听起来神神秘秘,其 实不过是“绝对轴距总和”,用到八数码上,相当与将所有数字归位需要的最少移动次数总和。作为启发函数,自然需要满足“境界五”提到的那两个条件。现在来 看这个曼哈顿距离,第一个条件自然满足。对于第二个,因为空格被我们剥离出去,所以交换的时候只关心交换的那个数字,它至多向目标前进1,而深度作为g每 次是增加1的,这样g+h至少和原来相等,那么,第二个条件也满足了。A*可用了,而且,有了个更加优化的启发函数。

      境界七、A*+哈希+曼哈顿距离+小顶堆

      经过上面优化后,我们发现了A*也有些鸡肋的地方,因为需要每次找到所谓Open表中f最小的元素,如果每次排序,那么排序的工作量可以说是很 大的,即使是快排,程序也不够快!这里,可以想到,由于需要动态的添加元素,动态的得到程序的最小值,我们可以维护一个小顶堆,这样的效果就是。每次取最 小元素的时候,不是用一个n*Log(n)的排序,而是用log(n)的查找和调整堆,好,算法又向前迈进了一大步。

      境界八、IDA*+曼哈顿距离

      IDA*即迭代加深的A*搜索,实现代码是最简练的,无须状态判重,无需估价排序。那么就用不到哈希表,堆上也不必应用,空间需求变的超级少。 效率上,应用了曼哈顿距离。同时可以根据深度和h值,在找最优解的时候,对超过目前最优解的地方进行剪枝,这可以导致搜索深度的急剧减少,所以,这,是一 个致命的剪枝!因此,IDA*大部分时候比A*还要快,可以说是A*的一个优化版本!


     以下为个人代码:

      1 #include <cstdio>
      2 #include <string.h>
      3 using namespace std;
      4 const int MAX = 362880, fact[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320}, dx[] = {0, -1, 1, 0}, dy[] = {-1, 0, 0, 1};
      5 bool vis[MAX] = {0};
      6 int LOG[MAX][9] = {1, 2, 3, 4, 5, 6, 7, 8, 0};
      7 struct node
      8 {
      9     char MOVE;
     10     int father;
     11 }path[MAX];
     12 bool TryToInsert(int Front, int Rear, int MOVE)
     13 {
     14     int i, j, cnt, Rcode = 0, Fcode = 0;
     15     for(i=0; i<9; ++i)
     16     {
     17         cnt = 0;
     18         for(j=i+1; j<9; ++j)
     19             if(LOG[Rear][i] > LOG[Rear][j])
     20                 ++cnt;
     21         Rcode += cnt * fact[8-i];
     22     }
     23     if(vis[Rcode])
     24         return false;
     25     else
     26     {
     27         for(i=0; i<9; ++i)
     28         {
     29             cnt = 0;
     30             for(j=i+1; j<9; ++j)
     31                 if(LOG[Front][i] > LOG[Front][j])
     32                     ++cnt;
     33             Fcode += cnt * fact[8-i];
     34         }
     35         path[Rcode].father = Fcode;
     36         switch(MOVE){
     37             case 0: path[Rcode].MOVE = 'd'; break;
     38             case 1: path[Rcode].MOVE = 'r'; break;
     39             case 2: path[Rcode].MOVE = 'l'; break;
     40             case 3: path[Rcode].MOVE = 'u'; break;
     41         }
     42         return vis[Rcode] = true;
     43     }
     44 }
     45 
     46 void BFS(void)
     47 {
     48     int Front = 0, Rear = 1, x, y, i, nx, ny, npos, opos;
     49     vis[46233] = true;
     50     do
     51     {
     52         for(opos=8; LOG[Front][opos]; --opos);
     53         x = opos % 3;
     54         y = opos / 3;
     55         for(int i=0; i<4; ++i)
     56         {
     57             nx = x + dx[i];
     58             ny = y + dy[i];
     59             if(nx >= 0 && nx <= 2 && ny >= 0 && ny <= 2)
     60             {
     61                 npos = ny*3 + nx;
     62                 memcpy(LOG[Rear], LOG[Front], 36);
     63                 LOG[Rear][npos] ^= LOG[Rear][opos], LOG[Rear][opos] ^= LOG[Rear][npos], LOG[Rear][npos] ^= LOG[Rear][opos];
     64                 if(TryToInsert(Front, Rear, i))
     65                     ++Rear;
     66             }
     67         }
     68         ++Front;
     69     } while (Front < Rear);
     70 }
     71 int main(void)
     72 {
     73     //freopen("in.txt", "r", stdin);
     74     int i, j, Zero, code, aim[9];
     75     char str[30];
     76     BFS();
     77     while(gets(str))
     78     {
     79         for(i=0, Zero=0; str[i]!='x'; ++i)
     80             if(str[i] != ' ')
     81                 ++Zero;
     82         str[i] = '0';
     83         sscanf(str, "%d%d%d%d%d%d%d%d%d", aim, aim+1, aim+2, aim+3, aim+4, aim+5, aim+6, aim+7, aim+8);
     84         code = 0;
     85         for(i=0; i<9; ++i)
     86         {
     87             int cnt = 0;
     88             for(j=i+1; j<9; ++j)
     89                 if(aim[i] > aim[j])
     90                     ++cnt;
     91             code += cnt * fact[8-i];
     92         }
     93         if(!vis[code])
     94             printf("unsolvable
    ");
     95         else
     96         {
     97             while(code != 46233)
     98             {
     99                 printf("%c", path[code].MOVE);
    100                 code = path[code].father;
    101             }
    102             printf("
    ");
    103         }
    104     }
    105 }
    HDU 1043
      1 #include <cstdio>
      2 #include <string.h>
      3 using namespace std;
      4 const int MAX = 362880, fact[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320}, dx[] = {0, -1, 1, 0}, dy[] = {-1, 0, 0, 1};
      5 bool vis[MAX] = {0};
      6 int LOG[MAX][9] = {1, 2, 3, 4, 5, 6, 7, 8, 0};
      7 struct node
      8 {
      9     char MOVE;
     10     int father;
     11 }path[MAX];
     12 bool TryToInsert(int Front, int Rear, int MOVE)
     13 {
     14     int i, j, cnt, Rcode = 0, Fcode = 0;
     15     for(i=0; i<9; ++i)
     16     {
     17         cnt = 0;
     18         for(j=i+1; j<9; ++j)
     19             if(LOG[Rear][i] > LOG[Rear][j])
     20                 ++cnt;
     21         Rcode += cnt * fact[8-i];
     22     }
     23     if(vis[Rcode])
     24         return false;
     25     else
     26     {
     27         for(i=0; i<9; ++i)
     28         {
     29             cnt = 0;
     30             for(j=i+1; j<9; ++j)
     31                 if(LOG[Front][i] > LOG[Front][j])
     32                     ++cnt;
     33             Fcode += cnt * fact[8-i];
     34         }
     35         path[Rcode].father = Fcode;
     36         switch(MOVE){
     37             case 0: path[Rcode].MOVE = 'd'; break;
     38             case 1: path[Rcode].MOVE = 'r'; break;
     39             case 2: path[Rcode].MOVE = 'l'; break;
     40             case 3: path[Rcode].MOVE = 'u'; break;
     41         }
     42         return vis[Rcode] = true;
     43     }
     44 }
     45 
     46 void BFS(void)
     47 {
     48     int Front = 0, Rear = 1, x, y, i, nx, ny, npos, opos;
     49     vis[46233] = true;
     50     do
     51     {
     52         for(opos=8; LOG[Front][opos]; --opos);
     53         x = opos % 3;
     54         y = opos / 3;
     55         for(int i=0; i<4; ++i)
     56         {
     57             nx = x + dx[i];
     58             ny = y + dy[i];
     59             if(nx >= 0 && nx <= 2 && ny >= 0 && ny <= 2)
     60             {
     61                 npos = ny*3 + nx;
     62                 memcpy(LOG[Rear], LOG[Front], 36);
     63                 LOG[Rear][npos] ^= LOG[Rear][opos], LOG[Rear][opos] ^= LOG[Rear][npos], LOG[Rear][npos] ^= LOG[Rear][opos];
     64                 if(TryToInsert(Front, Rear, i))
     65                     ++Rear;
     66             }
     67         }
     68         ++Front;
     69     } while (Front < Rear);
     70 }
     71 int main(void)
     72 {
     73     //freopen("in.txt", "r", stdin);
     74     int i, j, Zero, code, aim[9];
     75     char str[30];
     76     BFS();
     77     while(gets(str))
     78     {
     79         for(i=0, Zero=0; str[i]!='x'; ++i)
     80             if(str[i] != ' ')
     81                 ++Zero;
     82         str[i] = '0';
     83         sscanf(str, "%d%d%d%d%d%d%d%d%d", aim, aim+1, aim+2, aim+3, aim+4, aim+5, aim+6, aim+7, aim+8);
     84         code = 0;
     85         for(i=0; i<9; ++i)
     86         {
     87             int cnt = 0;
     88             for(j=i+1; j<9; ++j)
     89                 if(aim[i] > aim[j])
     90                     ++cnt;
     91             code += cnt * fact[8-i];
     92         }
     93         if(!vis[code])
     94             printf("unsolvable
    ");
     95         else
     96         {
     97             while(code != 46233)
     98             {
     99                 printf("%c", path[code].MOVE);
    100                 code = path[code].father;
    101             }
    102             printf("
    ");
    103         }
    104     }
    105 }
    POJ 1077
  • 相关阅读:
    统计创建对象个数
    动手动脑
    开学第一周心得
    放假第五周总结
    放假第四周总结
    第一周总结
    04-异常处理-动手动脑
    03-继承与多态-动手动脑
    02-类和对象-跟踪类对象创建个数
    02-类和对象-动手动脑
  • 原文地址:https://www.cnblogs.com/corvey/p/4779336.html
Copyright © 2011-2022 走看看