(一)问题描述
在一个3*3的方棋盘上放置着1,2,3,4,5,6,7,8八个数码,每个数码占一格,且有一个空格。这些数码可以在棋盘上移动,其移动规则是:与空格相邻的数码方格可以移入空格。现在的问题是:对于指定的初始棋局和目标棋局,给出数码的移动序列。该问题称八数码难题或者重排九宫问题。
(二)问题分析
八数码问题是个典型的状态图搜索问题。搜索方式有两种基本的方式,即树式搜索和线式搜索。搜索策略大体有盲目搜索和启发式搜索两大类。盲目搜索就是无“向导”的搜索,启发式搜索就是有“向导”的搜索。
1、启发式搜索
由于时间和空间资源的限制,穷举法只能解决一些状态空间很小的简单问题,而对于那些大状态空间的问题,穷举法就不能胜任,往往会导致“组合爆炸”。所以引入启发式搜索策略。启发式搜索就是利用启发性信息进行制导的搜索。它有利于快速找到问题的解。
由八数码问题的部分状态图可以看出,从初始节点开始,在通向目标节点的路径上,各节点的数码格局同目标节点相比较,其数码不同的位置个数在逐渐减少,最后为零。所以,这个数码不同的位置个数便是标志一个节点到目标节点距离远近的一个启发性信息,利用这个信息就可以指导搜索。即可以利用启发信息来扩展节点的选择,减少搜索范围,提高搜索速度。
启发函数设定。对于八数码问题,可以利用棋局差距作为一个度量。搜索过程中,差距会逐渐减少,最终为零,为零即搜索完成,得到目标棋局。
(三)数据结构与算法设计
该搜索为一个搜索树。为了简化问题,搜索树节点设计如下:
struct Chess//棋盘
{
int cell[N][N];//数码数组
int Value;//评估值
Direction BelockDirec;//所屏蔽方向
struct Chess * Parent;//父节点
};
int cell[N][N]; 数码数组:记录棋局数码摆放状态。
int Value; 评估值:记录与目标棋局差距的度量值。
Direction BelockDirec; 所屏蔽方向:一个屏蔽方向,防止回推。
Direction :enum Direction{None,Up,Down,Left,Right};//方向枚举
struct Chess * Parent; 父节点:指向父亲节点。
下一步可以通过启发搜索算法构造搜索树。
1、局部搜索树样例:
2、搜索过程
搜索采用广度搜索方式,利用待处理队列辅助,逐层搜索(跳过劣质节点)。搜索过程如下:
(1)、把原棋盘压入队列;
(2)、从棋盘取出一个节点;
(3)、判断棋盘估价值,为零则表示搜索完成,退出搜索;
(4)、扩展子节点,即从上下左右四个方向移动棋盘,生成相应子棋盘;
(5)、对子节点作评估,是否为优越节点(子节点估价值小于或等于父节点则为优越节点),是则把子棋盘压入队列,否则抛弃;
(5)、跳到步骤(2);
3、算法的评价
完全能解决简单的八数码问题,但对于复杂的八数码问题还是无能为力。现存在的一些优缺点。
1、可以改变数码规模(N),来扩展成N*N的棋盘,即扩展为N数码问题的求解过程。
2、 内存泄漏。由于采用倒链表的搜索树结构,简化了数据结构,但有部分被抛弃节点的内存没有很好的处理,所以会造成内存泄漏;
3、 采用了屏蔽方向,有效防止往回搜索(节点的回推),但没能有效防止循环搜索,所以不能应用于复杂度较大的八数码问题;
源码:
#include "stdio.h"
#include "stdlib.h"
#include "time.h"
#include "string.h"
#include <queue>
#include <stack>
using namespace std;
const int N=3;//3*3棋盘
const int Max_Step=30;//最大搜索深度
enum Direction{None,Up,Down,Left,Right};//方向
struct Chess//棋盘
{
int cell[N][N];//数码数组
int Value;//评估值
Direction BelockDirec;//所屏蔽方向
struct Chess * Parent;//父节点
};
//打印棋盘
void PrintChess(struct Chess *TheChess)
{
printf("------------------------------------------------------------------------/n");
for(int i=0;i<N;i++)
{
printf("/t");
for(int j=0;j<N;j++)
{
printf("%d/t",TheChess->cell[i][j]);
}
printf("/n");
}
printf("/t/t/t/t差距:%d/n",TheChess->Value);
}
//移动棋盘
struct Chess * MoveChess(struct Chess * TheChess,Direction Direct,bool CreateNewChess)
{
struct Chess * NewChess;
//获取空闲格位置
int i,j;
for(i=0;i<N;i++)
{
bool HasGetBlankCell=false;
for(j=0;j<N;j++)
{
if(TheChess->cell[i][j]==0)
{
HasGetBlankCell=true;
break;
}
}
if(HasGetBlankCell)
break;
}
//移动数字
int t_i=i,t_j=j;
bool AbleMove=true;
switch(Direct)
{
case Up:
t_i++;
if(t_i>=N)
AbleMove=false;
break;
case Down:
t_i--;
if(t_i<0)
AbleMove=false;
break;
case Left:
t_j++;
if(t_j>=N)
AbleMove=false;
break;
case Right:
t_j--;
if(t_j<0)
AbleMove=false;
break;
};
if(!AbleMove)//不可以移动则返回原节点
{
return TheChess;
}
if(CreateNewChess)
{
NewChess=new Chess();
for(int x=0;x<N;x++)
{
for(int y=0;y<N;y++)
NewChess->cell[x][y]=TheChess->cell[x][y];
}
}
else
NewChess=TheChess;
NewChess->cell[i][j]=NewChess->cell[t_i][t_j];
NewChess->cell[t_i][t_j]=0;
return NewChess;
}
//初始化一个初始棋盘
struct Chess * RandomChess(const struct Chess * TheChess)
{
int M=30;//随机移动棋盘步数
struct Chess * NewChess;
NewChess=new Chess();
memcpy(NewChess,TheChess,sizeof(Chess));
srand((unsigned)time(NULL));
for(int i=0;i<M;i++)
{
int Direct=rand()%4;
//printf("%d/n",Direct);
NewChess=MoveChess(NewChess,(Direction) Direct,false);
}
return NewChess;
}
//估价函数
int Appraisal(struct Chess * TheChess,struct Chess * Target)
{
int Value=0;
for(int i=0;i<N;i++)
{
for(int j=0;j<N;j++)
{
if(TheChess->cell[i][j]!=Target->cell[i][j])
Value++;
}
}
TheChess->Value=Value;
return Value;
}
//搜索函数
struct Chess * Search(struct Chess* Begin,struct Chess * Target)
{
Chess * p1,*p2,*p;
int Step=0;//深度
p=NULL;
queue<struct Chess *> Queue1;
Queue1.push(Begin);
//搜索
do{
p1=(struct Chess *)Queue1.front();
Queue1.pop();
for(int i=1;i<=4;i++)//分别从四个方向推导出新子节点
{
Direction Direct=(Direction)i;
if(Direct==p1->BelockDirec)//跳过屏蔽方向
continue;
p2=MoveChess(p1,Direct,true);//移动数码
if(p2!=p1)//数码是否可以移动
{
Appraisal(p2,Target);//对新节点估价
if(p2->Value<=p1->Value)//是否为优越节点
{
p2->Parent=p1;
switch(Direct)//设置屏蔽方向,防止往回推
{
case Up:p2->BelockDirec=Down;break;
case Down:p2->BelockDirec=Up;break;
case Left:p2->BelockDirec=Right;break;
case Right:p2->BelockDirec=Left;break;
}
Queue1.push(p2);//存储节点到待处理队列
if(p2->Value==0)//为0则,搜索完成
{
p=p2;
i=5;
}
}
else
{
delete p2;//为劣质节点则抛弃
p2=NULL;
}
}
}
Step++;
if(Step>Max_Step)
return NULL;
}while(p==NULL || Queue1.size()<=0);
return p;
}
main()
{
Chess * Begin,Target,* T;
//设定目标棋盘 [0 1 2],[3 4 5],[6 7 8]
for(int i=0;i<N;i++)
{
for(int j=0;j<N;j++)
{
Target.cell[i][j]=i*N+j;
}
}
//获取初始棋盘
Begin=RandomChess(&Target);
Appraisal(Begin,&Target);
Begin->Parent=NULL;
Begin->BelockDirec=None;
Target.Value=0;
printf("目标棋盘:/n");
PrintChess(&Target);
printf("初始棋盘:/n");
PrintChess(Begin);
//图搜索
T=Search(Begin,&Target);
//打印
if(T)
{
/*把路径倒序*/
Chess *p=T;
stack<Chess *>Stack1;
while(p->Parent!=NULL)
{
Stack1.push(p);
p=p->Parent;
}
printf("搜索结果:/n");
while(!Stack1.empty())
{
PrintChess(Stack1.top());
Stack1.pop();
}
printf("/n完成!");
}else
printf("搜索不到结果.深度为%d/n",Max_Step);
scanf("%d",T);
}