◆Vjudge◆◇广度优先搜索◇ Eight

来源:互联网 发布:知乎 菠萝斑马 编辑:程序博客网 时间:2024/06/05 17:43

◇广度优先搜索◇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 1213 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