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