题目大意:
只有一个测例,求解八数码问题,输入的是初始的八数码摆放位置,以一行显示,顺序是从上到下(行)从左到右显示,数字是1 ~ 8,空格用x表示,目标状态为排列1 ~ 8 + x,输出为空移动的方向,l、r、u、d的序列(无空格)表示左右上下。
A*:
注释代码:
/*
* Problem ID : POJ 1077 Eight
* Author : Lirx.t.Una
* Language : G++
* Run Time : 32 ms
* Run Memory : 3188 KB
*/
#pragma G++ optimize("O2")
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <queue>
//表示数码个数
#define N 8
//string length
//这里用字符串来存放数码状态
//加上x总共有9块砖
//最后一位留给'\0'
//因此长度为10
#define STRLEN 10
//maximum factorial number
//最大阶乘号
//由于有九个数,因此对应着9!种状态
//将不同的数码状态利用康托展开映射到9!个数
//91 = 362880
#define MAXFAC 362880
//maximum length of path
//即x移动的路径长度
//最大值经测试为25
#define MAXPATHLEN 25
//总共有4中x的移动方法,即上下左右
#define MOVN 4
//康托展开中所用到的阶乘个数
//由于数码中最大数字为9(将x转换为9)
//因此最大阶乘为(9-1)!
//阶乘从0!开始一直到8!
//因此为9中阶乘
#define FCN 9
using namespace std;
struct Node {//每个结点存放一种数码状态
//由于将每种状态都转化成一串1~9的字符了(x转换为9)
//因此可以组成一个9位数,没有超出int范围
//用int存比直接用字符串存节省空间(要考虑结构体数据对齐的情况)
int s;//即以int的形式数码字符串
//以下为A*的估价函数的参数:
//f = g + h
//g为解空间树中到达当前位置的代价(即步数,根结点代价为0,因此即为边的数量)
//h为当前状态到目标状态所要付出的代价,则个值一般以最小值计
//由于这里没有精确的最小值计算方法,因此只能用一些算法做近似
//这里h用h_val函数求得
int g;
int f;
Node(void) {}
Node( int ss, int gg, int hh ) :
s(ss), g(gg), f( gg + hh ) {}//hh传参时就已经用h_val求出了
bool
operator<(const Node &oth)//使用有限队列,让f最小者一直处于堆顶
const {
return f > oth.f;
}
};
struct Fath {//Father Node
//父结点
//这里利用数组模拟解空间树
//fath[i]就表示阶乘号为i的结点的父结点
//里面存放了父结点的阶乘号fac
//以及父结点到当前子结点的走法(lrud)movment
int fac;
char mov;
};
priority_queue<Node> q;//queue,利用STL的priority_queue来模拟优先队列(小顶堆)
Fath fath[MAXFAC];//解空间树
bool vist[MAXFAC];//have visited?vist[i]表示阶乘号为i的结点是否被访问过
char path[MAXPATHLEN];//从根结点触发到达目标结点过程中x所走过的路径
//即此问题的答案
//在移动x的时候将其动作转化到二维空间中的上下左右
//由于坐标系是朝右下角的,所以x正半轴朝下,y正半轴朝右
//(dx, dy)为x的移动向量
//分别对应着上下左右四种移动方式
int dx[MOVN] = { 0, 0, -1, 1 };
int dy[MOVN] = { -1, 1, 0, 0 };
char mv[MOVN] = { 'l', 'r', 'u', 'd' };
//对应着0!~8!这9种阶乘
int FC[FCN] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320 };
bool
cnsov(char *s) {//can solve this problem
//测试所给的初始状态能否到达目标状态
//目标状态就是排列123456789
//方法是求初始状态的逆序数
//注意!不包括空格x的序列的逆序数
int i, j;
int ord;//ordinal number,即逆序数的结果保存其中
for ( ord = 0, i = 0; i < N; i++ )
if ( '9' != s[i] )
for ( j = i + 1; j <= N; j++ )
if ( '9' != s[j] && s[i] > s[j] )
ord++;
if ( ord % 2 )//如果逆序数为奇数则不可解
return false;
return true;//否则可解
}
int
h_val(char *s) {//get h_value求估价函数的参数h
int i;
int h;
//这里采用当前状态中每个格子的值和目标状态中每个对应格子中的绝对差值的方法
//求和的方法估算h参数
//对于当前状态中i号格子中放的数就是s[i]
//而目标状态中i号格子中放的数是(i+'1')
for ( h = 0, i = 0; i <= N; i++ )
h += abs(s[i] - '1' - i);
return h;
}
int
hash(char *s) {//康托展开,即一个哈希函数
//将给定状态所表示的字符串映射到一个阶乘值上
//可以使每个状态都有不同的阶乘号
int i, j;
int ord;//每个i号位置数所拥有的逆序数
int fac;//最终的字符串哈希值,即阶乘号
for ( fac = 0, i = 0; i < N; i++ ) {
for ( ord = 0, j = i + 1; j <= N; j++ )
if ( s[i] > s[j] )
ord++;
//康托展开的核心求法
//每一轮的逆序数乘以各自的阶乘数
fac += FC[ s[i] - '1' ] * ord;
}
return fac;
}
void
fndx( char *s, int &x, int &y ) {//find x
//找到状态中x的位置并求其二位坐标
int i;
for ( i = 0; '9' != s[i]; i++ );
x = i / 3;//横坐标
y = i % 3;//纵坐标
}
void
swp( char *s, int i, int j ) {//swap
//交换字符串中为位置i和位置j的字符
char tmp;
tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
int
a_star(char *sini) {//A*算法
//init string,根结点状态对应的字符串
Node nfath;//father node,父结点
char sfath[STRLEN];//father string,父结点状态对应的字符串
char schld[STRLEN];//children string,子结点对应的字符串
int fc;//children factorial number,子结点对应的阶乘号
//(x, y)为父结点状态中x的坐标
//(xx, yy)为子结点状态中x的坐标
int x, xx;
int y, yy;
int i;//计数变量
q.push( Node( atoi(sini), 0, h_val(sini) ) );//先将根结点入队
vist[ hash(sini) ] = true;//并将根结点置为访问过
while ( true ) {
//由于在main函数中已经先用cnsov函数判断过问题的可解性了
//因此这里就默认问题是可解的
//一定能return出a_star函数,因此这里就做了一个死循环
//先弹堆获得一个父结点
nfath = q.top();
q.pop();
//取出父结点的状态字符串并找到x的位置
sprintf(sfath, "%d", nfath.s);
fndx(sfath, x, y);
for ( i = 0; i < MOVN; i++ ) {//依次按照四种方向生成
//父结点的子结点
//子结点中x的位置是由父结点中x上下左右移动得来的
xx = x + dx[i];
yy = y + dy[i];
//1. 如果x走出了3×3框架范围则直接排除考察范围
if ( xx < 0 || xx > 2 || yy < 0 || yy > 2 )
continue;
//构造子结点的状态字符串
strcpy(schld, sfath);
swp( schld, 3 * x + y, 3 * xx + yy );
//2. 如果子结点已经被访问过了则也直接排除考察范围
if ( vist[ fc = hash(schld) ] )//必须通过获得子结点的阶乘号
//才可以获得它的vist的信息
continue;
//如果没有访问过则需要对其进行详细考察
vist[fc] = true;//将其置为访问过
fath[fc].fac = hash(sfath);//设置子结点的父结点的阶乘号
fath[fc].mov = mv[i];//从父结点走到子结点的方法也需设置
if ( !fc )//如果当前生成的子结点已经是目标结点了
//目标结点123456789的阶乘值为0(因为无逆序数)
return nfath.g + 1;//则直接返回从初始状态到目标状态的代价(即步数)
//否则需要将子结点入队待下次有机会再考察
q.push( Node( atoi(schld), nfath.g + 1, h_val(schld) ) );
}
}
}
int
main() {
char sini[STRLEN];//初始状态字符串
char c;//每次接受一个字符
int i;//计数变量
for ( i = 0; i <= N; i++ ) {
scanf("%s", &c);
if ( 'x' == c )
sini[i] = '9';
else
sini[i] = c;
}
sini[i] = '\0';
if ( !cnsov(sini) ) {
puts("unsolvable");
return 0;
}
int fc;//children factorial number,子结点阶乘号
Fath * pf;//pointer to father node,父结点指针
i = a_star(sini);//i此时为path的长度
//从0到i-1存放完整路径
path[i--] = '\0';//左后以为放空字符
fc = 0;//子结点指针初始化为0,即目标结点的阶乘号
pf = fath;//父结点指针初始化为目标结点的父节点
//从后往前构造path
while ( i >= 0 ) {
path[i--] = pf->mov;//父结点到子结点的走法
fc = pf->fac;//将子结点上移为父结点
pf = fath + fc;//将父结点上移为父父结点,即更新完的子结点的父结点
}
puts(path);//输出
return 0;
}
无注释代码:
#pragma G++ optimize("O2")
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <queue>
#define N 8
#define STRLEN 10
#define MAXFAC 362880
#define MAXPATHLEN 25
#define MOVN 4
#define FCN 9
using namespace std;
struct Node {
int s;
int g;
int f;
Node(void) {}
Node( int ss, int gg, int hh ) :
s(ss), g(gg), f( gg + hh ) {}
bool
operator<(const Node &oth)
const {
return f > oth.f;
}
};
struct Fath {
int fac;
char mov;
};
priority_queue<Node> q;
Fath fath[MAXFAC];
bool vist[MAXFAC];
char path[MAXPATHLEN];
int dx[MOVN] = { 0, 0, -1, 1 };
int dy[MOVN] = { -1, 1, 0, 0 };
char mv[MOVN] = { 'l', 'r', 'u', 'd' };
int FC[FCN] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320 };
bool
cnsov(char *s) {
int i, j;
int ord;
for ( ord = 0, i = 0; i < N; i++ )
if ( '9' != s[i] )
for ( j = i + 1; j <= N; j++ )
if ( '9' != s[j] && s[i] > s[j] )
ord++;
if ( ord % 2 )
return false;
return true;
}
int
h_val(char *s) {
int i;
int h;
for ( h = 0, i = 0; i <= N; i++ )
h += abs(s[i] - '1' - i);
return h;
}
int
hash(char *s) {
int i, j;
int ord;
int fac;
for ( fac = 0, i = 0; i < N; i++ ) {
for ( ord = 0, j = i + 1; j <= N; j++ )
if ( s[i] > s[j] )
ord++;
fac += FC[ s[i] - '1' ] * ord;
}
return fac;
}
void
fndx( char *s, int &x, int &y ) {
int i;
for ( i = 0; '9' != s[i]; i++ );
x = i / 3;
y = i % 3;
}
void
swp( char *s, int i, int j ) {
char tmp;
tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
int
a_star(char *sini) {
Node nfath;
char sfath[STRLEN];
char schld[STRLEN];
int fc;
int x, xx;
int y, yy;
int i;
q.push( Node( atoi(sini), 0, h_val(sini) ) );
vist[ hash(sini) ] = true;
while ( true ) {
nfath = q.top();
q.pop();
sprintf(sfath, "%d", nfath.s);
fndx(sfath, x, y);
for ( i = 0; i < MOVN; i++ ) {
xx = x + dx[i];
yy = y + dy[i];
if ( xx < 0 || xx > 2 || yy < 0 || yy > 2 )
continue;
strcpy(schld, sfath);
swp( schld, 3 * x + y, 3 * xx + yy );
if ( vist[ fc = hash(schld) ] )
continue;
vist[fc] = true;
fath[fc].fac = hash(sfath);
fath[fc].mov = mv[i];
if ( !fc )
return nfath.g + 1;
q.push( Node( atoi(schld), nfath.g + 1, h_val(schld) ) );
}
}
}
int
main() {
char sini[STRLEN];
char c;
int i;
for ( i = 0; i <= N; i++ ) {
scanf("%s", &c);
if ( 'x' == c )
sini[i] = '9';
else
sini[i] = c;
}
sini[i] = '\0';
if ( !cnsov(sini) ) {
puts("unsolvable");
return 0;
}
int fc;
Fath * pf;
i = a_star(sini);
path[i--] = '\0';
fc = 0;
pf = fath;
while ( i >= 0 ) {
path[i--] = pf->mov;
fc = pf->fac;
pf = fath + fc;
}
puts(path);
return 0;
}
IDA*:
注释代码:
无注释代码:
单词解释:
sliding:adj, 可滑动的,变化的
tile:n, 瓦片,瓷砖
pack:vt, 包装,塞满,捆扎
frame:n, 框架
object:n, 目标
scramble:vt, 使混杂,搅乱,攀登
slightly:adv, 稍微地,轻微地
letter:n, 字母
row:n, 行,排
Sam Loyd:人名,山姆·劳埃德,历史上第一个构造出不可解的15数码问题
frustrate:vt, 挫败,使感到灰心
compose of:由...组成
configuration:n, 配置,结构