POJ 1077 Eight

1 篇文章 0 订阅

题目大意:

        只有一个测例,求解八数码问题,输入的是初始的八数码摆放位置,以一行显示,顺序是从上到下(行)从左到右显示,数字是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, 配置,结构

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值