◆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
- ◆Vjudge◆◇广度优先搜索◇ Eight
- Eight poj1077 广度优先搜索
- 广度优先搜索算法
- 双向广度优先搜索
- 广度优先搜索法
- pku2251(广度优先搜索)
- POJ1184Clerver_Writer:广度优先搜索
- Java广度优先搜索
- [AI]广度优先搜索
- 广度优先搜索-BFS
- 广度优先搜索算法
- 广度优先搜索算法
- BFS广度优先搜索
- 广度优先搜索 BFS
- 广度优先搜索
- [ 算法 ]广度优先搜索!
- 广度优先搜索
- 广度优先搜索算法
- Android Studio Opengl Cmake 配置 编译 jni c++ 调用opengl
- EasyX中loadimage函数和putimage函数如何调用
- redis启动报错解决
- Java Set
- C语言练习题(7)
- ◆Vjudge◆◇广度优先搜索◇ Eight
- python模块调用时,相对路径问题
- 冒泡排序法C语言代码
- Java 语言中的 volatile 变量
- maven pom.xml随笔
- LeetCode-Easy刷题(11) Maximum Subarray
- Symantec/DigiCer证书认证平台联合升级计划
- SpannableString用法详解
- e的x次方的导数为什么是e^x?lnx的导数为什么是1/x?