◆Vjudge◆◇广度优先搜索◇ Eight - 八数码问题

◇广度优先搜索◇Eight - 八数码问题


◆题外话◆

说实话,作者对这道题深有感触……TLE了6次——编码、hash、set都试了,最后发现是代码写的丑 ~~o(>_<)o ~~
—— 在此献上 Blog 一篇


◆题目◆

Description

The 15-puzzle has been around for over 100 years; even if you don’t know it by that name, you’ve seen it. It is 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 the missing tile ‘x’; the object of the puzzle is to arrange the tiles so that they are ordered as:

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

where the only legal operation is to exchange ‘x’ with one of the tiles with which it shares an edge. As an example, 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 10 12    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 previous row indicate which neighbor of the ‘x’ tile is swapped with the ‘x’ tile at each 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 version of the puzzle, and
frustrating many people. In fact, all you have to do to make a regular puzzle into an unsolvable one is to swap two tiles (not counting the missing ‘x’ tile, of course).
In this problem, you will write a program for solving the less well-known 8-puzzle, composed of tiles on a three by three
arrangement.

Input

You will receive a description of a configuration of the 8 puzzle. The description is just a list of the tiles in their initial positions, with the rows listed from top to bottom, and the tiles listed from left to right within a row, where the tiles are represented by 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 standard output either the word “unsolvable”, if the puzzle has no solution, or a string consisting entirely of the letters ‘r’, ‘l’, ‘u’ and ‘d’ that describes a series of moves that produce a solution. The string should include no spaces and start at the beginning of the line.

Sample Input
 2  3  4  1  5  x  7  6  8 
Sample Output
ullddrurdllurdruldr

◆题目解析◆

看到题 —— 一个 3*3 的矩阵 —— 搜索!然后又看到网上说 八数码问题 的解决方式很多,所以不暇思索选择了 广度优先搜索。
写了一半,突然发现 —— 八数码怎么判重?于是在网上一搜,oh……

(1) 变量声明

由于题目要求输出路径,用 STL 的 queue<> 就不太方便,所以直接用手写队列 que,以结构体 Queue 作为变量类型 —— 包含:①八数码的位置按题目描述转换为一个链状数组后相连而成的 int 变量 num (在用 编码 的方法时,num为一维 int 数组);②达到这一状态的上一步所在 que 中的下标 father;③从上一步移动到这个状态所移动的方向 F(从0~3依次是 上左下右);④当前空位所在 num 中的位置(从高位到低位)where 。
接下来是几个需要用到的常量数组——
①方向控制 int 数组 F ,由于是以一维形式存储的 3*3 的矩阵,一维数组中的第 i 个元素对应的上左下右的元素分别是 i-3,i-1,i+1,i+3 ,但是向上要满足 i>3,向下要满足 i≤6 ,向左要满足([x]表示x向下取整) [(i-2)/3]=[(i-1)/3],向右要满足 [i/3]==[(i-1)/3] 。这样就构成了移动所必须满足的条件。
②输出方案 char 数组 Char 分别对应 上左下右 Char[4]={'u','l','d','r'}
③取整型数中某一位的数字要用到的 long long 数组 ten_n ,ten_n_i=10^i 。

(2) 交换整型数中指定2个位置上的数字

八数码矩阵转换为整型数时必然是9位(把空位变为0),写一个函数 change(n,a,b) 用于将一个9位的整型数 n 的第 a 位与第 b 位交换(从高位开始为第一位),即是如下函数:

const long long ten_n[]={1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000,10000000000};
long long change(int n,int a,int b) //记得是long long !
{
    long long A=n%ten_n[9-a+1]/ten_n[9-a],B=n%ten_n[9-b+1]/ten_n[9-b],Ret; //取出第a、b位上的数
    Ret=n-A*ten_n[9-a]-B*ten_n[9-b]; //将n的第a、b位去除
    Ret=Ret+B*ten_n[9-a]+A*ten_n[9-b]; //将 A、B 换位重新放入 n
    return Ret;
}
(3)广度优先搜索

将起点的 father、F 初始化为 -1 ,套一下模板:

while(l<r) //l 队头所在位置, r队尾所在位置
{
    Queue Front=que[l];
    for(int i=0;i<4;i++)
    {
        Queue Push=Front;
        Push.F=i;Push.where=Front.where+F[i];Push.father=l;
        if(Push.where<=0 || Push.where>9 || (i%2 && (Push.where-1)/3!=(Front.where-1)/3)) continue;
        Push.num=(int)change((long long)Push.num,Front.where,Push.where);
        /*判重*/
        que[r++]=Push;
        if(Push.num==123456780)
        {
            int G=r-1;
            vector<int> vec; //逆序存储答案
            while(true) //找“爸爸”
            {
                vec.push_back(que[G].F);
                G=que[G].father;
                if(G==-1) break;
            }
            for(int j=vec.size()-2;j>=0;j--)
                printf("%c",Char[vec[j]]);
            puts("");
            return 0;
        }
    }
    l++;
}
(4)判重
① STL set<> (如果有 O2 优化的话也能 AC)

这是最简单但是速度最慢的方法(网上也是这么说的,的确是三个方法唯一一个 TLE 的)。也就是利用 STL set<> 的 .count(x) ——可以查找在该容器中是否存在 x(时间复杂度近似于 log n)。如果没有查找到就用 .insert(x) 将该元素存入 set ,作标记。

② hash

其实就是把原数字模一个 Mod(Mod越大越好,但是不要超内存)得到 y,然后存入一个 vector vec[y],由于不同的数字模 Mod 可能相同,因此每次需要查找 vec[y] 中是否存在 x ,这样的优化就是将所有出现过的数字以模 Mod 得到的数分为不同集合,从而减少查找规模。

③逆序对编码

这个时候结构体中的 num 用数组就比较好了。由于每一个数字的逆序对的位置不相同,可以用此特点编码,最大会出现 9!=362880 ,因此数组只需要362880这么大就可以了。


◆代码片◆

第一次尝试用 set<> - TLE:
/*Lucky_Glass*/
#include<cstdio>
#include<algorithm>
#include<set>
#include<vector>
using namespace std;
const long long ten_n[]={1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000,10000000000};
const int F[4]={-3,-1,3,1};
const char Char[4]={'u','l','d','r'};
struct Queue
{
    int num,father,F,where;
}que[700000];
set<int> flag;
long long change(int n,int a,int b)
{
    long long A=n%ten_n[9-a+1]/ten_n[9-a],B=n%ten_n[9-b+1]/ten_n[9-b],Ret;
    Ret=n-A*ten_n[9-a]-B*ten_n[9-b];
    Ret=Ret+B*ten_n[9-a]+A*ten_n[9-b];
    return Ret;
}
int main()
{
    int l=0,r=0;
    Queue begin;
    begin.num=0;begin.father=begin.F=-1;
    for(int i=1,num;i<=9;i++)
    {
        char c='\0';
        while(!('0'<=c && c<='9') && c!='x') c=getchar();
        if(c=='x') begin.num*=10,begin.where=i;
        else begin.num=begin.num*10+c-'0';
    }
    que[r++]=begin;
    while(l<r)
    {
        Queue Front=que[l];
        for(int i=0;i<4;i++)
        {
            Queue Push=Front;
            Push.F=i;Push.where=Front.where+F[i];Push.father=l;
            if(Push.where<=0 || Push.where>9 || (i%2 && (Push.where-1)/3!=(Front.where-1)/3)) continue;
            Push.num=(int)change((long long)Push.num,Front.where,Push.where);
            if(flag.count(Push.num)) continue;
            flag.insert(Push.num);
            que[r++]=Push;
            if(Push.num==123456780)
            {
                int G=r-1;
                vector<int> vec;
                while(true)
                {
                    vec.push_back(que[G].F);
                    G=que[G].father;
                    if(G==-1) break;
                }
                for(int j=vec.size()-2;j>=0;j--)
                    printf("%c",Char[vec[j]]);
                puts("");
                return 0;
            }
        }
        l++;
    }
    puts("unsolvable");
    return 0;
}
第二次试了一试 hash - AC:
/*Lucky_Glass*/
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
const long long ten_n[]={1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000,10000000000};
const int MOD=1000003;
const int F[4]={-3,-1,3,1};
const char Char[4]={'u','l','d','r'};
struct Queue
{
    int num,father,F,where;
}que[700000];
vector<int> flag[MOD];
bool found(int n)
{
    int Size=flag[n%MOD].size();
    for(int i=0;i<Size;i++)
        if(flag[n%MOD][i]==n)
            return true;
    return false;
}
long long change(int n,int a,int b)
{
    long long A=n%ten_n[9-a+1]/ten_n[9-a],B=n%ten_n[9-b+1]/ten_n[9-b],Ret;
    Ret=n-A*ten_n[9-a]-B*ten_n[9-b];
    Ret=Ret+B*ten_n[9-a]+A*ten_n[9-b];
    return Ret;
}
int main()
{
    int l=0,r=0;
    Queue begin;
    begin.num=0;begin.father=begin.F=-1;
    for(int i=1,num;i<=9;i++)
    {
        char c='\0';
        while(!('0'<=c && c<='9') && c!='x') c=getchar();
        if(c=='x') begin.num*=10,begin.where=i;
        else begin.num=begin.num*10+c-'0';
    }
    que[r++]=begin;
    while(l<r)
    {
        Queue Front=que[l];
        for(int i=0;i<4;i++)
        {
            Queue Push=Front;
            Push.F=i;Push.where=Front.where+F[i];Push.father=l;
            if(Push.where<=0 || Push.where>9 || (i%2 && (Push.where-1)/3!=(Front.where-1)/3)) continue;
            Push.num=(int)change(Push.num,Push.where,Front.where);
            if(found(Push.num)) continue;
            flag[Push.num%MOD].push_back(Push.num);
            que[r++]=Push;
            if(Push.num==123456780)
            {
                int G=r-1;
                vector<int> vec;
                while(true)
                {
                    vec.push_back(que[G].F);
                    G=que[G].father;
                    if(G==-1) break;
                }
                for(int j=vec.size()-2;j>=0;j--)
                    printf("%c",Char[vec[j]]);
                puts("");
                return 0;
            }
        }
        l++;
    }
    printf("unsolvable\n");
    return 0;
}
这个是AC编码
/*Lucky_Glass*/
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
const int H[15]={1,1,2,6,24,120,720,5040,40320,362880,3628800};
const int F[4]={-3,-1,3,1};
const char Char[4]={'u','l','d','r'};
struct Queue
{
    int num[15],father,F,where;
}que[600000];
bool vis[362885];
bool End(int n[])
{
    for(int i=0;i<8;i++)
        if(n[i]!=i+1)
            return false;
    return true;
}
bool found(int n[])
{
    int Sum=0;
    for(int i=0;i<9;i++)
        for(int j=i+1;j<9;j++) 
            if(n[j]<n[i]) 
                Sum+=H[8-i];
    if(vis[Sum]) return true;
    vis[Sum]=true;
    return false;
}
int main()
{
    int l=0,r=0;
    Queue begin;
    begin.father=begin.F=-1;
    for(int i=0,num;i<9;i++)
    {
        char c='\0';
        while(!('0'<=c && c<='9') && c!='x') c=getchar();
        if(c=='x') begin.num[i]=0,begin.where=i;
        else begin.num[i]=c-'0';
    }
    que[r++]=begin;
    while(l<r)
    {
        Queue Front=que[l];
        for(int i=0;i<4;i++)
        {
            Queue Push=Front;
            Push.F=i;Push.where=Front.where+F[i];Push.father=l;
            if(Push.where<0 || Push.where>=9 || (i%2 && Push.where/3!=Front.where/3)) continue;
            swap(Push.num[Push.where],Push.num[Front.where]);
            if(found(Push.num)) continue;
            que[r++]=Push;
            if(End(Push.num))
            {
                int G=r-1;
                vector<int> vec;
                while(true)
                {
                    vec.push_back(que[G].F);
                    G=que[G].father;
                    if(G==-1) break;
                }
                for(int j=vec.size()-2;j>=0;j--)
                    printf("%c",Char[vec[j]]);
                puts("");
                return 0;
            }
        }
        l++;
    }
    printf("unsolvable\n");
    return 0;
}

The End

Thanks for reading!

-Lucky_Glass

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值