http://blog.sina.com.cn/s/blog_a01c59f101015tjd.html
八数码问题
这是课程中的一个实验,问题比较有趣,所有代码都是本人自行设计编写,所以特意写出来供大家学习和参考,做到这个实验的同学千万不要完整复制我写的代码哦,参考学习一下,你可以自己写出更好的代码。
问题描述:
给定九宫格的初始状态,要求在有限步的操作内,使其转化为目标状态,且所得到的解是代价最小解(即移动的步数最少)。例如:
2 3 1 2 3
1 8 4 -> 8 4
7 6 5 7 6 5
求解思路:
用启发式搜索算法求解,A*算法。
首先定义了六个结构体:一是表示九宫格的各个数字,二是表示OPEN结点,三是表示OPEN表,四是表示CLOSED结点,五是表示CLOSED表,六是表示求解路径。自定义的函数有:初始化九宫格;判断两个九宫格是否一致;求某结点的hx值,即与目标结点九宫格不一样的元素个数;对OPEN表按照估价函数值的大小从小到大排序;判断某九宫格是否与OPEN表中某结点相同;判断某九宫格是否与CLOSED表中某结点相同;将一个九宫格中的数字全部复制到另一个九宫格中;求OPEN表中某结点的扩展结点;删除OPEN表中第一个结点;计算估价函数值并赋值;以矩阵形式打印九宫格;打印求解最终路径;启发式搜索函数寻找求解路径。
算法描述:
procedure heuristic_search
open :=[start] ; closed:=[ ] ;f(s) := g(s) + h(s) ;
while open != [ ] do
begin
从open 表中删除第一个状态,称为n ;
if n = 目的状态 then return (success) ;
生成n 的所有子状态;
if n 没有任何子状态 then continue ;
for n 的每个子状态 do
case 子状态 is not already on open 表 or closed 表 :
begin
计算该子状态的估价函数值;
将该子状态加到open 表中;
end ;
case 子状态 is already on open 表 :
if 该子状态是沿着一条比在open 表已有的更短路径而到达
then 记录更短路径走向及其估价函数值;
case 子状态 is already on closed 表:
if 该子状态是沿着一条比在closed 表已有的更短路径而到达
begin
将该子状态从closed 表移到 open 表中;
记录更短路径走向及其估价函数值;
end ;
case end ;
将n 放入closed 表中;
根据估价函数值,从小到大重新排列open 表;
end ;
return (failure);
End
求解代码:
#include
#include
#define MAXOPEN 300
#define MAXCLOSE 600
#define MAXPATH 300
//自定义结构体
typedef struct{
int num[3][3]; //九宫格数字
}Squared; //九宫格
typedef struct{
Squared stateself; //九宫格
int dx; //结点在搜索树中的深度
int hx; //结点对应九宫格与目标九宫格不一样的元素个数
int fx; //估价函数fx=dx+hx
}Open; //OPEN结点
typedef struct{
Open squanote[MAXOPEN]; //记录未扩展结点
Open squapare[MAXOPEN]; //未扩展结点对应的父结点
}OpenSqua; //OPEN表记录未扩展结点
typedef struct{
int num; //CLOSED结点代价值
Squared stateself; //九宫格
}Closed; //CLOSED结点
typedef struct{
Closed squanote[MAXCLOSE]; //记录已扩展结点
}ClosedSqua; //CLOSED表记录已扩展结点
typedef struct{
Open pa[MAXPATH]; //保存求解路径
}Path; //求解路径
//自定义结构体
//初始化九宫格
void InitSqua(Squared &s){
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
s.num[i][j]=0; //3*3矩阵各数字初始化为0
}
}
}
//初始化九宫格
//判断两个九宫格是否一致
bool EqualSqua(Squared a,Squared b){
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
if(a.num[i][j]!=b.num[i][j])
return 0; //如果有不同的元素就返回0
}
}
return 1; //一致就返回1
}
//判断两个九宫格是否一致
//求某结点的hx值,即与目标结点九宫格不一样的元素个数
int GetHx(Squared a,Squared end){
int count=0;
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
if(a.num[i][j]!=end.num[i][j])
count++;
}
}
return count;
}
//求某结点的hx值,即与目标结点九宫格不一样的元素个数
//对OPEN表按照估价函数值的大小从小到大排序
void SortOpen(OpenSqua &op,int n){
int i,j;
for(i=0;i
int index=i;
for(j=i+1;j
if(op.squanote[j].fx
index=j;
if(index!=i){
Open temp=op.squanote[i];
op.squanote[i]=op.squanote[index];
op.squanote[index]=temp;
}
}
}
}
//对OPEN表按照估价函数值的大小从小到大排序
//判断某九宫格是否与OPEN表中某结点相同
bool ExistOpen(OpenSqua op,Squared s){
for(int i=0;i
if(EqualSqua(op.squanote[i].stateself,s))
return 1;
}
return 0;
}
//判断某九宫格是否与OPEN表中某结点相同
//判断某九宫格是否与CLOSED表中某结点相同
bool ExistClosed(ClosedSqua close,Squared s){
for(int i=0;i
if(EqualSqua(close.squanote[i].stateself,s))
return 1;
}
return 0;
}
//判断某九宫格是否与CLOSED表中某结点相同
//将一个九宫格中的数字全部复制到另一个九宫格中
void CopySqua(Squared &a,Squared b){
for(int i=0;i<3;i++){
for(int j=0;j<3;j++){
a.num[i][j]=b.num[i][j];
}
}
}
//将一个九宫格中的数字全部复制到另一个九宫格中
//求OPEN表中某结点的扩展结点
bool CreateSub(Open s[],Open note){
int i,j,k=0;
for(i=0;i<3;i++){
for(j=0;j<3;j++){
if(note.stateself.num[i][j]==0){ //寻找九宫格空缺所在的坐标
if(i-1>=0){ //向上移动
CopySqua(s[k].stateself,note.stateself);
s[k].stateself.num[i][j]=note.stateself.num[i-1][j];
s[k].stateself.num[i-1][j]=note.stateself.num[i][j];
s[k].dx=1;
k++;
}
if(j+1<=2){ //向右移动
CopySqua(s[k].stateself,note.stateself);
s[k].stateself.num[i][j]=s[k].stateself.num[i][j+1];
s[k].stateself.num[i][j+1]=0;
s[k].dx=1;
k++;
}
if(i+1<=2){ //向下移动
CopySqua(s[k].stateself,note.stateself);
s[k].stateself.num[i][j]=s[k].stateself.num[i+1][j];
s[k].stateself.num[i+1][j]=0;
s[k].dx=1;
k++;
}
if(j-1>=0){ //向左移动
CopySqua(s[k].stateself,note.stateself);
s[k].stateself.num[i][j]=s[k].stateself.num[i][j-1];
s[k].stateself.num[i][j-1]=0;
s[k].dx=1;
k++;
}
return 1;
}
}
}
return 0;
}
//求OPEN表中某结点的扩展结点
//删除OPEN表中第一个结点
void DeleteOne(OpenSqua &a,int cnt){
int i;
for(i=0;i
a.squanote[i]=a.squanote[i+1];
}
}
//删除OPEN表中第一个结点
//计算估价函数值并赋值
void CalEvaluate(Open &op,Open pare,Squared end){
op.dx=pare.dx+1; //求dx
op.hx=GetHx(op.stateself,end); //求hx
op.fx=op.dx+op.hx; //求fx
}
//计算估价函数值并赋值
//以矩阵形式打印九宫格
void PrintSqua(Squared s){
for(int i=0;i<3;i++){
cout<<"\t";
for(int j=0;j<3;j++){
if(s.num[i][j]==0)
cout<<" ";
else
cout<
}
cout<<"\n";
}
}
//以矩阵形式打印九宫格
//打印求解最终路径
void ShowPath(Path path,int cnt){
int i,j,smp=cnt-1,count=0;
Open *endPath;
endPath=new Open[cnt];
for(i=smp;i>0;i--){
i=smp;
for(j=i-1;j>=0;j--){
if(GetHx(path.pa[j].stateself,path.pa[i].stateself)==2){
endPath[count++]=path.pa[i];
break;
}
}
smp=j;
}
endPath[count++]=path.pa[0];
for(i=count-1;i>=0;i--){
PrintSqua(endPath[i].stateself);
cout<<"\n";
}
}
//打印求解最终路径
//启发式搜索函数寻找求解路径
bool Heuristic_Search(Squared start,Squared end){
int cntop=0,cntcl=0,cntpa=0;
int count=0;
OpenSqua open; //OPEN表
ClosedSqua close; //CLOSED表
Path path; //求解路径
Open nop; //标记为n的结点
open.squanote[cntop].stateself=start; //初始化OPEN
open.squanote[cntop].dx=0; //初始化OPEN
open.squanote[cntop].hx=GetHx(open.squanote[cntop].stateself,end); //初始化OPEN
open.squanote[cntop].fx=open.squanote[cntop].dx+open.squanote[cntop].hx; //初始化OPEN
cntop++;
while(cntop!=0){ //当OPEN表不为空
nop=open.squanote[0]; //把OPEN表中第一个结点给标记为n的结点
path.pa[cntpa++]=nop; //记录求解路径
DeleteOne(open,cntop); //从OPEN表中删除第一个状态
cntop--;
if(EqualSqua(nop.stateself,end)){
ShowPath(path,cntpa); //如果找到了目标状态则输出求解路径
return 1;
}
Open sub[4]; //扩展结点最多4个
CreateSub(sub,nop); //初始化n结点的扩展结点
if(!CreateSub(sub,nop))
continue; //如果没有扩展结点就跳出进行下一次循环
for(int i=0;i<4;i++){
if(sub[i].dx==1){ //对于n的每个存在的子状态
if(!ExistOpen(open,sub[i].stateself)&&!ExistClosed(close,sub[i].stateself)){
CalEvaluate(sub[i],nop,end);
open.squanote[cntop]=sub[i];
open.squapare[cntop]=nop;
cntop++;
continue;
}
if(ExistOpen(open,sub[i].stateself)){
CalEvaluate(sub[i],nop,end);
if(sub[i].fx
open.squanote[cntop]=sub[i];
open.squapare[cntop]=nop;
cntop++;
continue;
}
}
if(ExistClosed(close,sub[i].stateself)){
CalEvaluate(sub[i],nop,end);
if(sub[i].fx
open.squanote[cntop]=sub[i];
open.squapare[cntop]=nop;
cntop++;
continue;
}
}
}
}
close.squanote[cntcl].stateself=nop.stateself; //将n放入closed 表中;
close.squanote[cntcl].num=nop.fx; //将n放入closed 表中;
cntcl++;
SortOpen(open,cntop); //根据估价函数值,从小到大重新排列open 表
if(count++>300){
break;
}
}
return 0;
}
//启发式搜索函数寻找求解路径
//在主函数中测试
void main(){
cout<<"========================九宫重排问题========================\n";
cout<<"问题描述:\n\t给定九宫格的初始状态,要求在有限步的操作内,使其转化为\n";
cout<<" 目标状态,且所得到的解是代价最小解(即移动的步数最少)\n";
cout<<" 例如输入 1 2 3 4 0 5 6 7 8 \n";
cout<<"则表示你所要表示的九宫格为:\n";
cout<<"\t1 2 3\n\t4 5\n\t6 7 8\n";
cout<<"========================输入始末状态========================\n";
Squared inits,goals; //初始与目标状态九宫格
int i,j,x;
InitSqua(inits); //初始化初始状态九宫格
InitSqua(goals); //初始化目标状态九宫格
cout<<"请输入九宫的初始状态:(数字为空时用0替代)\n";
for(i=0;i<3;i++){
for(j=0;j<3;j++){
cin>>x;
inits.num[i][j]=x;
}
}
cout<<"您输入的初始状态为:\n";
PrintSqua(inits); ////打印初始状态九宫格
cout<<"请输入九宫的目标状态:(数字为空时用0替代)\n";
for(i=0;i<3;i++){
for(j=0;j<3;j++){
cin>>x;
goals.num[i][j]=x;
}
}
cout<<"您输入的目标状态为:\n";
PrintSqua(goals); ////打印目标状态九宫格
cout<<"========================打印求解路径========================\n";
cout<<"求解路径为:\n";
if(Heuristic_Search(inits,goals)){
cout<<"success!\n";
}else{
cout<<"no result!\n";
}
cout<<"========================问题演示结束========================\n";
}
//在主函数中测试
心得体会:
本题程序的编写基本上就是花了一个比较长的整时间进行的编写。刚开始没有一点头绪,想想每种策略都会有很多分支,不知如何取舍,在仔细阅读书本中有关A*算法的描述后,结合实验指导书提供的算法伪代码,基本上确立了整体的框架,在进入主题之前,我首先解决的是九宫格的输入输出问题,使得不仅方便输入,而且输出的九宫格也很形象。结合算法中所需要的操作,又编写了复制九宫格、计算代价、排序、判断某九宫格是否在某一表中、求扩展结点等辅助函数。最后在核心算法函数中进行调用,根据A*算法,编写出了搜索函数,在测试过程中,每次都从最简单的情况开始测试,寻找错误的地方,一步一步进行纠正,最终将书上的案例数据都测试通过了,不过本程序有一些缺陷,我使用了定长顺序表来存放选择步骤,所以这个对于简单的案例会造成占用了较多的空间,却没有用上,而对于复杂的案例,也许顺序表不够长,最终无法得出正确结果。考虑到本题没有判断某数据是否有解的函数,所以只能一步一步搜索,为防止无止境的搜索,我仍然没有改变定长顺序表,这样当搜索进行一定步骤之后,如果还是无解,我就认为这是无解的。最后,同样对界面进行了优化。
这个程序最大的缺陷就是,我也不知道是否能解所有问题,比较简单的测试数据可以得到正确解,而有的测试数据,也不知道是否真的无解。这个问题比较纠结了。个人感觉结构体设计的不太好,浪费空间资源,数据有些冗余,有待改进。
谢谢!