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

状态总数是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;
}

 

posted on 2010-07-19 18:34  yongmou-  阅读(8606)  评论(12编辑  收藏  举报