BFS(五):八数码难题 (POJ 1077)
Eight
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
(1)编程思路1。
1)定义结点
用数组来表示棋盘的布局,如果将棋盘上的格子从左上角到右下角按0到8编号,就可用一维数组board[9]来顺序表示棋盘上棋子的数字,空格用“9”表示,数组元素的下标是格子编号。为方便处理,状态结点还包括该布局的空格位置space,否则需要查找“9”在数组中的位置才能确定空格的位置。另外,为节约存储空间,将数组类型定义为char(每个元素只占一个字节存储空间,还是可以存储整数1~9)。因此在程序中,定义状态结点为结构数据类型:
struct node{
char board[9];
char space; // 空格所在位置
};
2)状态空间
由于棋盘有9个格子,每种布局可以看成是数字1~9的一个排列,因此全部的布局数应为9!(362880)种。为了便于判断两种布局是否为同一种布局,可以编写一个函数int hash(const char *s)把数字1~9的排列映射为一个整数num(0<=num<=(9!-1))。例如,排列“123456789”映射为0、“213456789”映射为1、“132456789”映射为2、“231456789”映射为3、…、“987654312”映射为362878、“987654321”映射为362879。
这样,每种状态就可以对应一个整数。反过来说,0~ (9!-1)之间的任一整数,也可以唯一对应一种状态。因此,判断两个状态结点cur和nst是否为同一种状态,只需判断 hash(cur.board)和hash(nst.board)是否相等即可,无需对两个格局数组的每个元素进行交互比较。
为保存状态空间,定义三个全局数据:
char visited[MAXN]; // visited[i]=1表示状态i被访问过;为0,表示未被访问
int parent[MAXN]; // parent[i]=k 表示状态结点i是由结点k扩展来的
char move[MAXN]; // move[i]=d 表示状态结点i是由结点k按照方式d扩展来的
3)结点扩展规则
棋子向空格移动实际上是空格向相反方向移动。设空格当前位置是cur.space ,则结点cur的扩展规则为:
空格向上移动,cur.space=cur.space-3;空格向左移动,cur.space=cur.space-1;空格向右移动,cur.space=cur.space+1;空格向下移动,cur.space=cur.space+3。
设向上移动k=0、向左移动k=1、向右移动k=2、向下移动k=3,则上述规则可归纳为一条:空格移动后的位置为cur.space=cur.space-5+2*(k+1)。为此,定义一个数组
const char md[4] = {'u', 'l', 'r', 'd'};
表示这四种移动方向。
空格的位置cur.space<3,不能上移;空格的位置cur.space>5,不能下移;空格的位置cur.space是3的倍数,不能左移;空格的位置cur.space+1是3的倍数,不能右移。
4)搜索策略
将初始状态start放入队列中,求出start对应的hash值k = hash(start.board),并置 parent[k] = -1、visited[k] = 1。
① 从队列头取一个结点,按照向上、向左、向右和向下的顺序,检查移动空格后是否可以产生新的状态nst。
② 如果移动空格后有新状态产生,则检查新状态nst是否已在队列中出现过(visited[hash(nst.board)]== 1),是则放弃,返回①。
③ 如果新状态nst未在队列中出现过,就将它加入队列,再检查新状态是否目标状态(hash(nst.board)==0),如果是,则找到解,搜索结束;否则返回①。
(2)源程序1。
#include<iostream>
#include<queue>
using namespace std;
struct node{
char board[9];
char space; // 空格所在位置
};
const int MAXN = 362880;
int fact[]={ 1, 1,2,6,24,120,720,5040,40320};
// 对应 0!,1!,2!,3!,4!,5!,6!,7!,8!
int hash(const char *s)
// 把1..9的排列*s 映射为数字 0..(9!-1)
{
int i, j, temp, num;
num = 0;
for (i = 0; i < 9-1; i++)
{
temp = 0;
for (j = i + 1; j < 9; j++)
{
if (s[j] < s[i])
temp++;
}
num += fact[9-i-1] * temp;
}
return num;
}
char visited[MAXN];
int parent[MAXN];
char move[MAXN];
const char md[4] = {'u', 'l', 'r', 'd'};
void BFS(const node & start)
{
int k, i;
node cur, nst;
for(k=0; k<MAXN; ++k)
visited[k] = 0;
k = hash(start.board);
parent[k] = -1;
visited[k] = 1;
queue <node> que;
que.push(start);
while(!que.empty())
{
cur = que.front();
que.pop();
for(i=0; i<4; ++i)
{
if(!(i==0 && cur.space<3 || i==1 && cur.space%3==0 || i==2 && cur.space%3==2 ||i==3 && cur.space>5))
{
nst = cur;
nst.space = cur.space-5+2*(i+1);
nst.board[cur.space]=nst.board[nst.space];
nst.board[nst.space]=9;
k = hash(nst.board);
if(visited[k] != 1)
{
move[k] = i;
visited[k] = 1;
parent[k] = hash(cur.board);
if(k == 0) //目标结点hash值为0
return;
que.push(nst);
}
}
}
}
}
void print_path()
{
int n, u;
char path[1000];
n = 1;
path[0] = move[0];
u = parent[0];
while(parent[u] != -1)
{
path[n] = move[u];
++n;
u = parent[u];
}
for(int i=n-1; i>=0; --i)
{
cout<<md[path[i]];
}
cout<<endl;
}
int main()
{
node start;
char ch;
int i;
for(i=0; i<9; ++i)
{
cin>>ch;
if(ch == 'x')
{
start.board[i] = 9;
start.space = i;
}
else
start.board[i] = ch - '0';
}
for (i = 0; start.board[i] == i+1 && i < 9; ++i) ;
if (i == 9) cout<<endl;
else
{
BFS(start);
if(visited[0] == 1)
print_path();
else
cout<<"unsolvable"<<endl;
}
return 0;
}
将上面的源程序提交给POJ系统,系统显示的评测结果是:Accept,Memory为3844K、Time为 782MS。
(3)编程思路2。
状态空间的表示、结点的扩展规则与编程思路1中的方法基本相同。但结点稍作修改,定义为: struct state { char a[N]; }; 不再定义空格的位置,并且程序中空格用“0”表示。对于某一当前状态cur,执行一个循环 for (i = 0; cur.a[i] && i < N; ++i) ;后,就可以确定空格位置 space=i。
定义全局数组 state Q[MAXN+1]来作为一个队列使用,全局数组char vis[MAXN]来表示状态结点是否被访问,其中vis[i]=0,表示状态i未被访问过;vis[i]=1,表示状态i是正向扩展(从初始状态开始)来访问过的;vis[i]=2,表示状态i是反向扩展(从目标状态开始)来访问过的。全局数组foot p[MAXN]用来存储访问过的每种状态的访问足迹, 其中,p[nt].k = ct表示状态nt是由状态结点ct扩展来的,p[nt].d = i(i为0~3之一)表示状态nt是由状态结点ct按方式i扩展来的。
用front和rear变量指示队列的队头和队尾。初始化时,初始状态start和目标状态goal均入队。
Q[front = 1] = start;
Q[rear = 2] = goal;
vis[hash(start)] = 1; // 1 代表正向
vis[hash(goal)] = 2; // 2 代表反向
(4)源程序2。
#include <iostream>
using namespace std;
# define N 9
# define MAXN 362880
struct foot { int k; char d;};
struct state { char a[N]; };
const char md[4] = {'u', 'l', 'r', 'd'};
const int fact[9] = {1, 1, 2, 6, 24, 120, 720, 720*7, 720*56};
state Q[MAXN+1];
char vis[MAXN];
foot p[MAXN];
int hash(state s)
// 把状态s中的0..8的排列映射为数字 0..(9!-1)
{
int i, j, temp, num;
num = 0;
for (i = 0; i < 9-1; i++)
{
temp = 0;
for (j = i + 1; j < 9; j++)
{
if (s.a[j] < s.a[i])
temp++;
}
num += fact[8-i] * temp;
}
return num;
}
void print_path(int x, char f)
{
if (p[x].k == 0) return ;
if (f) cout<< md[3-p[x].d];
print_path(p[x].k, f);
if (!f) cout<< md[p[x].d];
}
void bfs(state start, state goal)
{
char t;
state cur, nst;
int front, rear, i;
int space, ct, nt;
Q[front = 1] = start;
Q[rear = 2] = goal;
vis[hash(start)] = 1; // 1 代表正向
vis[hash(goal)] = 2; // 2 代表反向
while (front <= rear)
{
cur = Q[front++];
ct = hash(cur);
for (i = 0; cur.a[i] && i < N; ++i) ;
space=i;
for (i = 0; i < 4; ++i)
{
if(!(i==0 && space<3 || i==1 && space%3==0 || i==2 && space%3==2 ||i==3 && space>5))
{
nst = cur;
nst.a[space] = cur.a[space-5+2*(i+1)];
nst.a[space-5+2*(i+1)] = 0;
nt = hash(nst);
if (!vis[nt])
{
Q[++rear] = nst;
p[nt].k = ct;
p[nt].d = i;
vis[nt] = vis[ct];
}
else if (vis[ct] != vis[nt])
{
t = (vis[ct]==1 ? 1:0);
print_path(t ? ct:nt, 0);
cout<< md[t ? i:3-i];
print_path(t ? nt:ct, 1);
cout<<endl;
return ;
}
}
}
}
cout<<"unsolvable"<<endl;
}
int main()
{
char i, ch;
state start, goal;
for (i = 0; i < N; ++i)
{
cin>>ch;
start.a[i] = (ch=='x' ? 0:ch-'0');
}
goal.a[8] = 0;
for (i = 0; i < N-1; ++i)
goal.a[i] = i + 1;
for (i = 0; start.a[i] == goal.a[i] && i < N; ++i) ;
if (i == N)
cout<<endl;
else
bfs(start, goal);
return 0;
}
将上面的源程序提交给POJ系统,系统显示的评测结果是:Accept,Memory为3420K、Time为 16MS 。从系统返回的评测结果看,采用双向广度优先搜索算法,搜索效率大幅提高。
BFS(五):八数码难题 (POJ 1077)的更多相关文章
- 习题:八数码难题(双向BFS)
八数码难题(wikioi1225) [题目描述] 在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字.棋盘中留有一个空格,空格用0来表示.空格周围的棋子可以移到空格中.要求解的问题是:给出 ...
- 双向广搜+hash+康托展开 codevs 1225 八数码难题
codevs 1225 八数码难题 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 钻石 Diamond 题目描述 Description Yours和zero在研究A*启 ...
- Codevs 1225 八数码难题
1225 八数码难题 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 钻石 Diamond 题目描述 Description Yours和zero在研究A*启发式算法.拿到一道经典的 ...
- [luogu]P1379 八数码难题[广度优先搜索]
八数码难题 ——!x^n+y^n=z^n 我在此只说明此题的一种用BFS的方法,因为本人也是初学,勉勉强强写了一个单向的BFS,据说最快的是IDA*(然而蒟蒻我不会…) 各位如果想用IDA*的可以看看 ...
- 洛谷P1379八数码难题
题目描述 在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字.棋盘中留有一个空格,空格用0来表示.空格周围的棋子可以移到空格中. 要求解的问题是:给出一种初始布局(初始状态)和目标布局(为 ...
- 洛谷——P1379 八数码难题
P1379 八数码难题 双向BFS 原来双向BFS是这样的:终止状态与起始状态同时入队,进行搜索,只不过状态标记不一样而已,本题状态使用map来存储 #include<iostream> ...
- codevs1225八数码难题(搜索·)
1225 八数码难题 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 钻石 Diamond 题解 题目描述 Description Yours和zero在研究A*启 ...
- 洛谷 P1379 八数码难题 解题报告
P1379 八数码难题 题目描述 在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字.棋盘中留有一个空格,空格用0来表示.空格周围的棋子可以移到空格中.要求解的问题是:给出一种初始布局(初 ...
- 【洛谷P1379】八数码难题(广搜、A*)
八数码难题 题目描述 一.广搜: 首先要考虑用什么存每一个状态 显然每个状态都用一个矩阵存是很麻烦的. 我们可以考虑将一个3*3的矩阵用一个字符串或long long 存. 每次扩展时再转化为矩阵. ...
- 「LuoguP1379」 八数码难题(迭代加深
[P1379]八数码难题 - 洛谷 题目描述 在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字.棋盘中留有一个空格,空格用0来表示.空格周围的棋子可以移到空格中.要求解的问题是:给出一种 ...
随机推荐
- 怎么给罗技K480 增加Home、End键
最近看张大妈上很多人分享了我的桌面,有感于整天低头码字不利健康,隧鼓捣起自己的电脑桌了. 此处省略N字... 进入正文,我码字用的是罗技的K480蓝牙键盘 码了几行代码,发现没有Home.End键,这 ...
- Feature extraction - sklearn文本特征提取
http://blog.csdn.net/pipisorry/article/details/41957763 文本特征提取 词袋(Bag of Words)表征 文本分析是机器学习算法的主要应用领域 ...
- [AC自己主动机+可能性dp] hdu 3689 Infinite monkey theorem
意甲冠军: 给n快报,和m频率. 然后进入n字母出现的概率 然后给目标字符串str 然后问m概率倍的目标字符串是敲数量. 思维: AC自己主动机+可能性dp简单的问题. 首先建立trie图,然后就是状 ...
- SDL(01-10)
SDL中的函数需要先初始化SDL才能用 : //Initialize SDL ) { printf( "SDL could not initialize! SDL_Error: %s\n&q ...
- 在WPF程序中将控件所呈现的内容保存成图像
原文:在WPF程序中将控件所呈现的内容保存成图像 有的时候,我们需要将控件所呈现的内容保存成图像保存下来,例如:InkCanvas的手写墨迹,WebBrowser中的网页等.可能有人会说,这个不就是截 ...
- Image Caption论文合辑2
说明: 这个合辑里面的论文不全是Image Caption, 但大多和Image Caption相关, 同时还有一些Workshop论文. Guiding Long-Short Term Memory ...
- 启动组织重整 Marvell追求创新文化
最近接任Marvell技术长的Neil Kim正是该公司亟需的人才——他在今年四月加入后,预计将为Marvell带来正面.积极的改革契机,有机会让该公司彻底改头换面... 迈威尔科技(Marvell) ...
- sqlserver从xlsx读取数据
exec sp_configure 'show advanced options',1 reconfigure exec sp_configure 'Ad Hoc Distributed Querie ...
- C#基础加强篇----委托、Lamada表达式和事件(上)
1.委托 C#的委托相当于C/C++中的函数指针.函数指针用指针获取一个函数的入口地址,实现对函数的操作. 委托与C/C++中的函数指针不同在于,委托是面向对象的,是引用类型,对委托的使用要先定义后实 ...
- git服务器创建,冲突解决,远程仓库获取指定文件
1.git服务器创建 在公司多人协作开发的情况下,不能简单地使用github,因为github是互联网公开的,这种情况公司的代码的保密性就会丧失了.这种情况下,需要创建git服务器. 登录服务器,使用 ...