1.问题描述

在一个3*3的方棋盘上放置着1,2,3,4,5,6,7,8八个数码,每个数码占一格,且有一个空格。这些数码可以在棋盘上移动,其移动规则是:与空格相邻的数码方格可以移入空格。现在的问题是:对于指定的初始棋局和目标棋局,给出数码的移动序列。该问题称八数码难题或者重排九宫问题。

 

2.问题分析

八数码问题是个典型的状态图搜索问题。搜索方式有两种基本的方式,即树式搜索和线式搜索。搜索策略大体有盲目搜索和启发式搜索两大类。盲目搜索就是无“向导”的搜索,启发式搜索就是有“向导”的搜索。

启发式搜索:由于时间和空间资源的限制,穷举法只能解决一些状态空间很小的简单问题,而对于那些大状态空间的问题,穷举法就不能胜任,往往会导致“组合爆炸”。所以引入启发式搜索策略。启发式搜索就是利用启发性信息进行制导的搜索。它有利于快速找到问题的解。

由八数码问题的部分状态图可以看出,从初始节点开始,在通向目标节点的路径上,各节点的数码格局同目标节点相比较,其数码不同的位置个数在逐渐减少,最后为零。所以,这个数码不同的位置个数便是标志一个节点到目标节点距离远近的一个启发性信息,利用这个信息就可以指导搜索。即可以利用启发信息来扩展节点的选择,减少搜索范围,提高搜索速度。

启发函数设定。对于八数码问题,可以利用棋局差距作为一个度量。搜索过程中,差距会逐渐减少,最终为零,为零即搜索完成,得到目标棋局。

 

3.编制程序实现求解8数码问题算法,采用估价函数f(n)=d(n)+w(n)/p(n),

其中:d(n)是搜索树中结点的深度;w(n)为结点n的数据库中错放的棋子个数;p(n)为结点n的数据库中每个棋子与其目标位置之间的距离总和。分析两种估价函数求解8数码问题的效率差别,给出一个是p(n)的上界的h(n)的定义,并测试使用该估价函数是否使算法失去可采纳性。

 

4.算法思想

1) int goal[N][N],struct Chessboard:

定义goal数组为目标状态——{1,2,3,8,0,4,7,6,5}。结构体Chessboard记录棋盘信息,其中变量pos数组坐标记录每个数码a的位置,其值为数码a。d表示该棋盘的深度,f为启发函数值,move为父节点移动到该节点的方式,以便在输出时告诉用户该如何移动棋子知道目标状态。

2)struct LNode:

定义节点LNode结构体,存放该节点状态时的棋盘信息board,和指向父节点、下一节点的指针(*parent,*next),以及标记量flag——值为true时表示该节点是最佳路径上的节点。

3)int* Findzero(LNode* &Node):

为方便找到空格,设计找到该函数Findzero(*&Node),以便找到当前节点空格所在位置以利于接下来的程序执行。返回值为空格所在的行和列。

4)int Wrong(LNode *Node)和int pick(LNode *Node):分别为函数ω(n)和p(n)

5) LNode* extend(LNode *Node,int depth,int zero[2],int moveflag,int Choose)

树形方式扩展节点。Node为要扩展的当前节点,depth为当前深度,zero存放该节点空格位置信息,moveflag即扩展节点的移动方式,Choose为选择函数ω(n)还是p(n)

6)void InitList(LNode* &Open,LNode* &Close)和int ListInsert(List &L,LNode* NewNode)

分别为表OPEN、CLOSE的创建和表的插入操作。

7)LNode* Getminf(List &L)主要是实现从OPEN表中选择一个f值最小的节点i。如果有几个节点值相同,当其中

有一个为目标节点时,则选择此目标节点;否则就选择其中任一个节点作为节。

 

5.源代码

#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
#define Overflow 1
#define N 3
int goal[N][N]={1,2,3,8,0,4,7,6,5};
int zero[2],NodeQTY=0;
int *z=zero;//记录0的位置,zero[0]:r行;zero[1]:c列
typedef int Piece;
struct Chessboard{//棋盘信息
Piece pos[N][N];//记录每个数码a的位置r行c列
int d,f,move;//d:深度;f:启发函数值 ;move:父节点移动到该节点的方式
};
struct LNode{
Chessboard board;
LNode *parent,*next;
bool flag;
};
typedef LNode* List;
int* Findzero(LNode* &Node)
{
int i,j,zr[2];
int *z=zr;
for(i=0;i<N;i++){
for(j=0;j<N;j++){
if(Node->board.pos[i][j]==0){
zr[0]=i+1;
zr[1]=j+1;
break;
}
}
}
return z;
}
int Wrong(LNode *Node)
{
int w=0,i,j;
for(i=0;i<N;i++){
for(j=0;j<N;j++){
if(Node->board.pos[i][j]!=goal[i][j]&&Node->board.pos[i][j]!=0)
w++;
}
}
return w;
}
int pick(LNode *Node)
{
int w=0,i,j,ii,jj;
for(i=0;i<N;i++){
for(j=0;j<N;j++){
if(Node->board.pos[i][j]!=goal[i][j]&&Node->board.pos[i][j]!=0){
for(ii=0;ii<N;ii++)
for(jj=0;jj<N;jj++)
if(Node->board.pos[i][j]==goal[ii][jj]){
w=w+abs(ii-i)+abs(jj-j);
break;
}
}
}
}
return w;
}
LNode* extend(LNode *Node,int depth,int zero[2],int moveflag,int Choose)
{
LNode* NewNode=new LNode;
for(int i=0;i<N;i++){
for(int j=0;j<N;j++){
NewNode->board.pos[i][j]=Node->board.pos[i][j];
}
}
switch(moveflag)
{
case 1: //向左移,不能出界:zero[1]>=2
NewNode->board.pos[zero[0]-1][zero[1]-1]=NewNode->board.pos[zero[0]-1][zero[1]-2];
NewNode->board.pos[zero[0]-1][zero[1]-2]=0;
break;
case 2: //向右移,不能出界:zero[1]<=2
NewNode->board.pos[zero[0]-1][zero[1]-1]=NewNode->board.pos[zero[0]-1][zero[1]];
NewNode->board.pos[zero[0]-1][zero[1]]=0;
break;
case 3: //向上移,不能出界:zero[0]>=2
NewNode->board.pos[zero[0]-1][zero[1]-1]=NewNode->board.pos[zero[0]-2][zero[1]-1];
NewNode->board.pos[zero[0]-2][zero[1]-1]=0;
break;
case 4: //向下移,不能出界:zero[0]<=2
NewNode->board.pos[zero[0]-1][zero[1]-1]=NewNode->board.pos[zero[0]][zero[1]-1];
NewNode->board.pos[zero[0]][zero[1]-1]=0;
break;
}
NewNode->board.d=depth+1;
switch(Choose){
case 1:NewNode->board.f=NewNode->board.d+Wrong(NewNode);break;
case 2:NewNode->board.f=NewNode->board.d+pick(NewNode);break;
}
NewNode->board.move=moveflag;
NewNode->parent=Node;
NodeQTY++;
return NewNode;
}
void InitList(LNode* &Open,LNode* &Close)
{
Open=(List)malloc(sizeof(LNode));
Close=(List)malloc(sizeof(LNode));
if(!Open&&!Close)
exit(Overflow);
Open->next=NULL;
Close->next=NULL;
}
int ListInsert(List &L,LNode* NewNode)
{
List p=L;
while(p->next){
p=p->next;
}
NewNode->next=p->next;
p->next=NewNode;
return true;
}
LNode* Getminf(List &L)
{
List p=L,q=L->next,r=L,min;
min=q;//p,q寻找f最小值的指针,r指向表L中min前一个元素
if(!q)
return NULL;
while(q)
{
if(min->board.f>q->board.f){
r=p;
min=q;
}
p=q;
q=q->next;
}
r->next=min->next;
min->next=NULL;
return min;
}

int main()
{
int i,j,choose;
List Open,Close;
LNode *Best,*current;
LNode *Start=new LNode;
printf("\t\t\t八 数 码 问 题 求 解\n");
printf("\n请输入初始状态:");
for(i=0;i<N;i++){
for(j=0;j<N;j++){
scanf("%d",&(Start->board.pos[i][j]));
}
}
printf("(注:The flag of movement--1:左移;2:右移;3:上移;4:下移)\n");
printf("初始棋盘状态:\n");
for(i=0;i<N;i++){
for(j=0;j<N;j++){
printf("|%d",Start->board.pos[i][j]);
}
printf("|\n");
}
InitList(Open,Close);
printf("请选择(1:A算法;2:A*算法):");
scanf("%d",&choose);
Start->board.d=0;
switch(choose){
case 1:Start->board.f=Start->board.d+Wrong(Start);break;
case 2:Start->board.f=Start->board.d+pick(Start);break;
} // Start->board.f=0+Wrong(Start);
Start->board.move=0;
Start->parent=NULL;
Start->next=NULL;
Start->flag=1;
ListInsert(Open,Start);//将S加入到Open表中
while(Open->next){
Best=Getminf(Open);
ListInsert(Close,Best);
if(!(Best->board.f-Best->board.d)){
printf("$$$******有解!******$$$\n");
break;
}
z=Findzero(Best);
zero[0]=*(z+0);zero[1]=*(z+1);
if(zero[1]>=N-1&&Best->board.move!=2)
ListInsert(Open,extend(Best,Best->board.d,zero,1,choose));
if(zero[1]<=N-1&&Best->board.move!=1)
ListInsert(Open,extend(Best,Best->board.d,zero,2,choose));
if(zero[0]>=N-1&&Best->board.move!=4)
ListInsert(Open,extend(Best,Best->board.d,zero,3,choose));
if(zero[0]<=N-1&&Best->board.move!=3)
ListInsert(Open,extend(Best,Best->board.d,zero,4,choose));
}
printf("本算法搜索图中总共扩展的节点数为:%d\n",NodeQTY);
printf("\t最佳路径如下所示:\n");
if(Open->next)
{
while(Best->parent){
Best->flag=1;
Best=Best->parent;
}
List p=Close->next,q=Close->next;
if(p==Start) q=p->next;
else exit(1);
int Step=0;
while(p&&q)//在Close表中依标记找到路径
{
if(q->flag==1&&q->parent==p){
printf("Step %d:0 move as
the %d-flag of movement.\n",++Step,q->board.move);
for(i=0;i<N;i++){
for(j=0;j<N;j++){
printf("|%d",q->board.pos[i][j]);
}
printf("|\n");
}
p=q;//记住父节点
}
q=q->next;
}
printf("到达目标状态!\n");
}
else printf("该问题无法求解!\n");
}