八数码 poj 1077 广搜 A* IDA*

经典的八数码问题,有人说不做此题人生不完整,哈哈。

状态总数是9! = 362880 种,不算太多,可以满足广搜和A*对于空间的需求。

状态可以每次都动态生成,也可以生成一次存储起来,我用的动态生成,《组合数学》书上有一种生成排列的方法叫做"序数法",我看了一会书,把由排列到序数,和由序数到排列的两个函数写了出来,就是代码中的int order(const char *s, int n) 和void get_node(int num, node &tmp)两个函数。

启发函数,用的是除空格外的八个数字到正确位置的网格距离。

几种方法的比较:广搜,效率最低,500ms;A*,32ms,已经比较高效了;IDA*, 0ms,空间也减少许多。A*为判重付出了巨大代价,时间 and 空间,uva上还有一个15数码的题,用A*肯定会爆空间的。IDA*不记录已经走过的路径,所以省去了空间,也省去了判断重复的步骤,但是会出现重复计算。

 

广搜:

 

代码
   
     
// BFS
#include < iostream >
#include
< cstdio >
#include
< queue >
using namespace std;

/* 把1..n的排列映射为数字 0..(n!-1) */
int fac[] = { 1 , 1 , 2 , 6 , 24 , 120 , 720 , 5040 , 40320 , 362880 }; // ...
int order( const char * s, int n) {
int i, j, temp, num;

num
= 0 ;

for (i = 0 ; i < n - 1 ; i ++ ) {
temp
= 0 ;
for (j = i + 1 ; j < n; j ++ ) {
if (s[j] < s[i])
temp
++ ;
}
num
+= fac[s[i] - 1 ] * temp;
}
return num;
}

bool is_equal( const char * b1, const char * b2){
for ( int i = 0 ; i < 9 ; i ++ )
if (b1[i] != b2[i])
return false ;
return true ;
}


// hash
struct node{
char board[ 9 ];
char space; // 空格所在位置
};

const int TABLE_SIZE = 362880 ;

int hash( const char * cur){
return order(cur, 9 );
}

/* 整数映射成排列 */
void get_node( int num, node & tmp) {
int n = 9 ;
int a[ 9 ]; // 求逆序数
for ( int i = 2 ; i <= n; ++ i) {
a[i
- 1 ] = num % i;
num
= num / i;
tmp.board[i
- 1 ] = 0 ; // 初始化
}
tmp.board[
0 ] = 0 ;
int rn, i;
for ( int k = n; k >= 2 ; k -- ) {
rn
= 0 ;
for (i = n - 1 ; i >= 0 ; -- i) {
if (tmp.board[i] != 0 )
continue ;
if (rn == a[k - 1 ])
break ;
++ rn;
}
tmp.board[i]
= k;
}
for (i = 0 ; i < n; ++ i)
if (tmp.board[i] == 0 ) {
tmp.board[i]
= 1 ;
break ;
}
tmp.space
= n - a[n - 1 ] - 1 ;
}

char visited[TABLE_SIZE];
int parent[TABLE_SIZE];
char move[TABLE_SIZE];
int step[ 4 ][ 2 ] = {{ - 1 , 0 },{ 1 , 0 }, { 0 , - 1 }, { 0 , 1 }}; // u, d, l, r

void BFS( const node & start){
int x, y, k, a, b;
int u, v;

for (k = 0 ; k < TABLE_SIZE; ++ k)
visited[k]
= 0 ;
u
= hash(start.board);
parent[u]
= - 1 ;
visited[u]
= 1 ;

queue
< int > que;
que.push(u);

node tmp, cur;
while ( ! que.empty()){
u
= que.front();
que.pop();

get_node(u, cur);

k
= cur.space;
x
= k / 3 ;
y
= k % 3 ;
for ( int i = 0 ; i < 4 ; ++ i){
a
= x + step[i][ 0 ];
b
= y + step[i][ 1 ];
if ( 0 <= a && a <= 2 && 0 <= b && b <= 2 ){
tmp
= cur;
tmp.space
= a * 3 + b;
swap(tmp.board[k], tmp.board[tmp.space]);
v
= hash(tmp.board);
if (visited[v] != 1 ){
move[v]
= i;
visited[v]
= 1 ;
parent[v]
= u;
if (v == 0 ) // 目标结点hash值为0
return ;

que.push(v);
}
}
}
}
}

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){
if (path[i] == 0 )
printf(
" u " );
else if (path[i] == 1 )
printf(
" d " );
else if (path[i] == 2 )
printf(
" l " );
else
printf(
" r " );
}
}

int main(){
freopen(
" in " , " r " , stdin);

node start;
char c;
for ( int i = 0 ; i < 9 ; ++ i){
cin
>> c;
if (c == ' x ' ){
start.board[i]
= 9 ;
start.space
= i;
}
else
start.board[i]
= c - ' 0 ' ;
}
BFS(start);

if (visited[ 0 ] == 1 )
print_path();
else
printf(
" unsolvable " );
return 0 ;
}

 

 

A*算法:

代码中对priority_queue<>模板的使用还是很有技巧性的,通过push一个最小的,再把它pop出来就解决了由于更改造成不一致性。前面这种做法是错误的,多谢一位朋友的提醒,这种方式确实不能保持堆的性质。不过我们可以采用冗余的办法,直接插入新值,这就不会破坏堆的性质了。

修改过的代码:

代码
   
     
// A*
#include < iostream >
#include
< cstdio >
#include
< cstring >
#include
< cstdlib >
#include
< queue >
using namespace std;

/* 把1..n的排列映射为数字 0..(n!-1) */
int fac[] = { 1 , 1 , 2 , 6 , 24 , 120 , 720 , 5040 , 40320 , 362880 }; // ...
int order( const char * s, int n) {
int i, j, temp, num;

num
= 0 ;

for (i = 0 ; i < n - 1 ; i ++ ) {
temp
= 0 ;
for (j = i + 1 ; j < n; j ++ ) {
if (s[j] < s[i])
temp
++ ;
}
num
+= fac[s[i] - 1 ] * temp;
}
return num;
}

bool is_equal( const char * b1, const char * b2){
for ( int i = 0 ; i < 9 ; i ++ )
if (b1[i] != b2[i])
return false ;
return true ;
}


// hash
struct node{
char board[ 9 ];
char space; // 空格所在位置
};

const int TABLE_SIZE = 362880 ;

int hash( const char * cur){
return order(cur, 9 );
}

/* 整数映射成排列 */
void get_node( int num, node & tmp) {
int n = 9 ;
int a[ 9 ]; // 求逆序数
for ( int i = 2 ; i <= n; ++ i) {
a[i
- 1 ] = num % i;
num
= num / i;
tmp.board[i
- 1 ] = 0 ; // 初始化
}
tmp.board[
0 ] = 0 ;
int rn, i;
for ( int k = n; k >= 2 ; k -- ) {
rn
= 0 ;
for (i = n - 1 ; i >= 0 ; -- i) {
if (tmp.board[i] != 0 )
continue ;
if (rn == a[k - 1 ])
break ;
++ rn;
}
tmp.board[i]
= k;
}
for (i = 0 ; i < n; ++ i)
if (tmp.board[i] == 0 ) {
tmp.board[i]
= 1 ;
break ;
}
tmp.space
= n - a[n - 1 ] - 1 ;
}

// 启发函数: 除去x之外到目标的网格距离和
int goal_state[ 9 ][ 2 ] = {{ 0 , 0 }, { 0 , 1 }, { 0 , 2 },
{
1 , 0 }, { 1 , 1 }, { 1 , 2 }, { 2 , 0 }, { 2 , 1 }, { 2 , 2 }};
int h( const char * board){
int k;
int hv = 0 ;
for ( int i = 0 ; i < 3 ; ++ i)
for ( int j = 0 ; j < 3 ; ++ j){
k
= i * 3 + j;
if (board[k] != 9 ){
hv
+= abs(i - goal_state[board[k] - 1 ][ 0 ]) +
abs(j
- goal_state[board[k] - 1 ][ 1 ]);
}
}
return hv;
}

int f[TABLE_SIZE], d[TABLE_SIZE]; // 估计函数和深度

// 优先队列的比较对象
struct cmp{
bool operator () ( int u, int v){
return f[u] > f[v];
}
};
char color[TABLE_SIZE]; // 0, 未访问;1, 在队列中,2, closed
int parent[TABLE_SIZE];
char move[TABLE_SIZE];
int step[ 4 ][ 2 ] = {{ - 1 , 0 },{ 1 , 0 }, { 0 , - 1 }, { 0 , 1 }}; // u, d, l, r

void A_star( const node & start){
int x, y, k, a, b;
int u, v;
priority_queue
< int , vector < int > , cmp > open;
memset(color,
0 , sizeof ( char ) * TABLE_SIZE);

u
= hash(start.board);
parent[u]
= - 1 ;
d[u]
= 0 ;
f[u]
= h(start.board);
open.push(u);
color[u]
= 1 ;

node tmp, cur;
while ( ! open.empty()){
u
= open.top();
if (u == 0 )
return ;
open.pop();

get_node(u, cur);

k
= cur.space;
x
= k / 3 ;
y
= k % 3 ;
for ( int i = 0 ; i < 4 ; ++ i){
a
= x + step[i][ 0 ];
b
= y + step[i][ 1 ];
if ( 0 <= a && a <= 2 && 0 <= b && b <= 2 ){
tmp
= cur;
tmp.space
= a * 3 + b;
swap(tmp.board[k], tmp.board[tmp.space]);
v
= hash(tmp.board);
if (color[v] == 1 && (d[u] + 1 ) < d[v]){ // v in open
move[v] = i;
f[v]
= f[v] - d[v] + d[u] + 1 ; // h[v]已经求过
d[v] = d[u] + 1 ;
parent[v]
= u;
// 直接插入新值, 有冗余,但不会错
open.push(v);
}
else if (color[v] == 2 && (d[u] + 1 ) < d[v]){ // v in closed
move[v] = i;
f[v]
= f[v] - d[v] + d[u] + 1 ; // h[v]已经求过
d[v] = d[u] + 1 ;
parent[v]
= u;
open.push(v);
color[v]
= 1 ;
}
else if (color[v] == 0 ){
move[v]
= i;
d[v]
= d[u] + 1 ;
f[v]
= d[v] + h(tmp.board);
parent[v]
= u;
open.push(v);
color[v]
= 1 ;
}
}
}
color[u]
= 2 ; //
}
}

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){
if (path[i] == 0 )
printf(
" u " );
else if (path[i] == 1 )
printf(
" d " );
else if (path[i] == 2 )
printf(
" l " );
else
printf(
" r " );
}
}

int main(){
// freopen("in", "r", stdin);

node start;
char c;
for ( int i = 0 ; i < 9 ; ++ i){
cin
>> c;
if (c == ' x ' ){
start.board[i]
= 9 ;
start.space
= i;
}
else
start.board[i]
= c - ' 0 ' ;
}
A_star(start);

if (color[ 0 ] != 0 )
print_path();
else
printf(
" unsolvable " );
return 0 ;
}

 

 

IDA*:
代码
    
      
// IDA*
#include < iostream >
#include
< cstdio >
#include
< cstdlib >
using namespace std;

#define SIZE 3

char board[SIZE][SIZE];

// 启发函数: 除去x之外到目标的网格距离和
int goal_state[ 9 ][ 2 ] = {{ 0 , 0 }, { 0 , 1 }, { 0 , 2 },
{
1 , 0 }, { 1 , 1 }, { 1 , 2 }, { 2 , 0 }, { 2 , 1 }, { 2 , 2 }};
int h( char board[][SIZE]){
int cost = 0 ;
for ( int i = 0 ; i < SIZE; ++ i)
for ( int j = 0 ; j < SIZE; ++ j){
if (board[i][j] != SIZE * SIZE){
cost
+= abs(i - goal_state[board[i][j] - 1 ][ 0 ]) +
abs(j
- goal_state[board[i][j] - 1 ][ 1 ]);
}
}
return cost;
}

int step[ 4 ][ 2 ] = {{ - 1 , 0 }, { 0 , - 1 }, { 0 , 1 }, { 1 , 0 }}; // u, l, r, d
char op[ 4 ] = { ' u ' , ' l ' , ' r ' , ' d ' };

char solution[ 1000 ];
int bound; // 上界
bool ans; // 是否找到答案
int DFS( int x, int y, int dv, char pre_move){ // 返回next_bound
int hv = h(board);
if (hv + dv > bound)
return dv + hv;
if (hv == 0 ){
ans
= true ;
return dv;
}

int next_bound = 1e9;
for ( int i = 0 ; i < 4 ; ++ i){
if (i + pre_move == 3 ) // 与上一步相反的移动
continue ;
int nx = x + step[i][ 0 ];
int ny = y + step[i][ 1 ];
if ( 0 <= nx && nx < SIZE && 0 <= ny && ny < SIZE){
solution[dv]
= i;
swap(board[x][y], board[nx][ny]);

int new_bound = DFS(nx, ny, dv + 1 , i);
if (ans)
return new_bound;
next_bound
= min(next_bound, new_bound);

swap(board[x][y], board[nx][ny]);
}
}
return next_bound;
}

void IDA_star( int sx, int sy){
ans
= false ;
bound
= h(board); // 初始代价
while ( ! ans && bound <= 100 ) // 上限
bound = DFS(sx, sy, 0 , - 10 );
}

int main(){
freopen(
" in " , " r " , stdin);

int sx, sy; // 起始位置
char c;
for ( int i = 0 ; i < SIZE; ++ i)
for ( int j = 0 ; j < SIZE; ++ j){
cin
>> c;
if (c == ' x ' ){
board[i][j]
= SIZE * SIZE;
sx
= i;
sy
= j;
}
else
board[i][j]
= c - ' 0 ' ;
}

IDA_star(sx, sy);

if (ans){
for ( int i = 0 ; i < bound; ++ i)
cout
<< op[solution[i]];
}
else
cout
<< " unsolvable " ;

return 0 ;
}

 

你可能感兴趣的:(poj)