设计一个启发函数,利用A*算法求解15数码问题。
5 | 1 | 2 | 4 |
9 | 6 | 3 | 8 |
13 | 15 | 10 | 11 |
14 | 0 | 7 | 12 |
1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 |
13 | 14 | 15 | 0 |
要求: 尽可能用与A*算法一致的思路实现算法, 力求简单明了地给出一个解.
15数码问题其实就是棋盘移动问题,是人工智能中的一个经典案例。如上图所示,在一个4 X 4的16宫格棋盘上,有15个数字将牌(整数1~15),还有一个空格(为了方便操作用数字0表示);空格周围四个方向的将牌允许向空格移动,通过移动将牌就可以改变将牌棋盘的格局。现在,我们要解决的问题就是如何移动将牌使初始状态成为目标状态。
在这里,我们将使用的是启发式搜索算法:A*算法。在使用A*算法之前,我们先来了解A算法。
启发式搜索算法A,一般简称为A算法,是一种典型的启发式搜索算法。其基本思想是:定义一个评价函数f,对当前的搜索状态进行评估,找出一个最有希望的节点来扩展。
评价函数的形式如下:
f(n)=g(n)+h(n)
其中n是被评价的节点。
我们先来定义下面几个函数的含义,它们与f(n)、g(n)和h(n)的差别是都带有一个"*"号。
g*(n):表示从初始节点s到节点n的最短路径的耗散值;
h*(n):表示从节点n到目标节点g的最短路径的耗散值;
f*(n)=g*(n)+h*(n):表示从初始节点s经过节点n到目标节点g的最短路径的耗散值。
而f(n)、g(n)和h(n)则分别表示是对f*(n)、g*(n)和h*(n)三个函数值的的估计值。是一种预测。A算法就是利用这种预测,来达到有效搜索的目的的。它每次按照f(n)值的大小对OPEN表中的元素进行排序,f值小的节点放在前面,而f值大的节点则被放在OPEN表的后面,这样每次扩展节点时,都是选择当前f值最小的节点来优先扩展。
当在算法A的评价函数中,使用的启发函数h(n)是处在h*(n)的下界范围,即满足h(n)<=h*(n)时,把这个算法就称为A*算法。
(1).把起始结点放到OPEN表中。计算F(S),并把其值与结点S联系起来。
(2).如果OPEN表是个空表,则没有解,失败退出;否则继续。
(3).从OPEN表中选择一个F值最小的结点I。如果有几个结点合格,当其中有一个为目标结点时,则选择此目标结点,否则就选择其中任一个结点为结点I。
(4).把结点I从OPEN表中移出,并把它放入CLOSE的扩展结点表中。
(5).如果I是目标结点,则成功退出,求得一个解。
(6).扩展结点I,生成其全部后继结点。对于I的每一个后继结点J:
(a).计算F(J).
(b).如果J既不在OPEN表中,也不在CLOSE表中,则用估价函数F把它添入OPEN表中。从J加一指向其父辈结点I的指针,以便一旦找到目标结点时记住一个解答捷径。
(c).如果J已在OPEN表或CLOSE表上,则比较刚刚对J计算过的F值和前面计算过的该结点在表中的F值。如果新的F值较小,则
(i).以此新值代替旧值。
(ii).从J指向I,而不是指向它的父辈结点。
(iii).如果结点J在CLOSE表中,则把它移回OPEN表中。
(7).转向(2),即GOTO(2)
首先,我们要建立一个结构体来存放信息:棋盘数据、移动规则、空格位置、是否遍历标志、f函数值、深度、父亲结点、儿子结点。
typedef struct QNode
{ int data[N][N]; //数据
int ancent; //标记方向左上右下分别为 1234 ,5为可以任意方向
int x; //标记0的横坐标
int y; //标记0的纵坐标
int gone; //是否遍历该节点,0未遍历,1遍历过
int value; //和目标的状态差=不在位将牌距离和+深度
int deep; //深度
struct QNode *father; //存放前一节点在"store"数组中的位置
struct QNode *next; //存放下一节点在"store"数组中的位置
}QNode, *QueuePtr;
接下来,我们要定义几个工具函数:
bool begin_opint():判断数据中是否有数字0(空格),即判断是否为正规数据;
bool compare(int a[N][N]):比较函数,判断当前数据状态是否与目标状态相同;
bool moveleft(int a[N][N], QueuePtr *b, int x, int y):数字0(空格)向左移动函数;
bool moveup(int a[N][N], QueuePtr *b, int x, int y):数字0(空格)向上移动函数;
bool moveright(int a[N][N], QueuePtr *b, int x, int y) :数字0(空格)向右移动函数;
bool movedown(int a[N][N], QueuePtr *b, int x, int y):数字0(空格)向下移动函数;
void output(QueuePtr *p):输出函数;
int getvalue(QueuePtr *p):计算耗散值函数,深度值+不在位将牌距离和。
然后,在主函数中利用这些函数使空格按照规则进行移动,边移动边比较目标状态,最后输出结果。
#include
#include
#include
#define N 4
typedef struct QNode{
int data[N][N]; //数据
int ancent; //标记方向左上右下分别为 1234 ,5为可以任意方向
int x; //标记0的横坐标
int y; //标记0的纵坐标
int gone; //是否遍历该节点,0未遍历,1遍历过
int value; //和目标的状态差=不在位将牌距离和+深度
int deep; //深度
struct QNode *father; //存放前一节点在"store"数组中的位置
struct QNode *next; //存放下一节点在"store"数组中的位置
}QNode, *QueuePtr;
typedef struct{
QueuePtr head; //头结点
QueuePtr rear; //尾结点
}LinkQueue;
int A[N][N]={ //目标状态
{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
{13,14,15,0}
};
int B[N][N]={ //初始状态
{5,1,2,4},
{9,6,3,8},
{13,15,10,11},
{14,0,7,12}
};
int x,y;
QueuePtr min; //存放最小的结点
bool begin_opint(){ //判断输入的数据是否合法
int i,j;
for(i=0;idata[i][j]=a[i][j];
}
k=(*b)->data[x][y];
(*b)->data[x][y]=(*b)->data[x][y-1];
(*b)->data[x][y-1]=k;
(*b)->x=x;
(*b)->y=y-1;
return true;
}
bool moveup(int a[N][N],QueuePtr *b,int x,int y){//向上移动函数
int k,i,j;
if(x==0)
return false;
for(i=0;idata[i][j]=a[i][j];
}
k=(*b)->data[x][y];
(*b)->data[x][y]=(*b)->data[x-1][y];
(*b)->data[x-1][y]=k;
(*b)->x=x-1;
(*b)->y=y;
return true;
}
bool movedown(int a[N][N],QueuePtr *b,int x,int y){ //向下移动函数
int k,i,j;
if(x==N-1)return false;
for(i=0;idata[i][j]=a[i][j];
}
k=(*b)->data[x][y];
(*b)->data[x][y]=(*b)->data[x+1][y];
(*b)->data[x+1][y]=k;
(*b)->x=x+1;
(*b)->y=y;
return true;
}
bool moveright(int a[N][N],QueuePtr *b,int x,int y){ //向右移动函数
int k,i,j;
if(y==N-1)
return false;
for(i=0;idata[i][j]=a[i][j];
}
k=(*b)->data[x][y];
(*b)->data[x][y]=(*b)->data[x][y+1];
(*b)->data[x][y+1]=k;
(*b)->x=x;
(*b)->y=y+1;
return true;
}
bool copy(QueuePtr *a){ //复制函数
int i,j;
for(i=0;idata[i][j]=A[i][j];
}
return true;
}
void output(QueuePtr *p){ //输出函数
int i,j;
long int n=0;
for(;(*p)->father!=NULL;(*p)=(*p)->father,n++){
for(i=0;idata[i][j]);
}printf("\n");
}printf("\n");
}
printf("step is %d\n",n-1);
}
int getvalue(QueuePtr *p){ //计算耗散值函数
int count=0;//保存距离
bool test=true; //若已找到一个位置的值则继续找下一个
//计算不在位的距离和
for(int i=0;ix||j!=(*p)->y)&&(*p)->data[i][j]==B[k][l]){
count=count+abs(i-k)+abs(j-l);
test=false;
}
if(test==false) break;
}
if(test==false) break;
}
}
}
count=count+(*p)->deep;//加上深度值
return count;
}
void main()
{
QueuePtr closed,p,q;
LinkQueue open;
if(!begin_opint()){
printf("no 0 opint!!\n"); //确定0点
exit(0);
}
open.head=open.rear=(QueuePtr)malloc(sizeof(QNode));//头结点
open.head->father=NULL;
open.rear->next=open.head->next=NULL;
closed=(QueuePtr)malloc(sizeof(QNode));//头结点
closed->next=NULL;
closed->father=NULL;
p=(QueuePtr)malloc(sizeof(QNode));//S0进open表
copy(&p);
p->x=x;
p->y=y;
p->ancent=5;
p->deep=0; //s0的深度为0
p->gone=0;
p->father=open.head;
p->value=getvalue(&p);
p->next=open.head->next;
open.head->next=p;
open.rear=open.head;
if(compare(p->data)){
output(&p);
exit(0);
}
while(open.head->next!=NULL){
//寻找最小状态
for(min=q=open.head->next;q!=NULL;q=q->next){
if(q->value<=min->value&&q->gone==0){
min=q;
break;
}
}
min->gone=1; //改最小状态已遍历
min->father->next=min->next; //在open表中删除找到的最小态
min->next=closed->next; //插入closed表的表头
closed->next=min;
//空格向4个方向移动
switch(closed->next->ancent){
case 1:p=(QueuePtr)malloc(sizeof(QNode));//祖先结点从右来 if(moveleft(closed->next->data,&p,closed->next->x,closed->next->y)){
p->father=closed->next;
p->ancent=1;
p->gone=0;
p->deep=min->deep+1;
p->value=getvalue(&p);
p->next=open.rear->next;
open.rear->next=p;
open.rear=p;
//比较输出结果
if(compare(p->data)){
output(&p);
exit(0);
}
}else free(p);
p=(QueuePtr)malloc(sizeof(QNode));
if(moveup(closed->next->data,&p,closed->next->x,closed->next->y)){
p->father=closed->next;
p->ancent=2;
p->gone=0;
p->deep=min->deep+1;
p->value=getvalue(&p);
p->next=open.rear->next;
open.rear->next=p;
open.rear=p;
//比较输出结果
if(compare(p->data)){
output(&p);
exit(0);
}
}else free(p);
p=(QueuePtr)malloc(sizeof(QNode));
if(movedown(closed->next->data,&p,closed->next->x,closed->next->y)){
p->father=closed->next;
p->ancent=3;
p->gone=0;
p->deep=min->deep+1;
p->value=getvalue(&p);
p->next=open.rear->next;
open.rear->next=p;
open.rear=p;
//比较输出结果
if(compare(p->data)){
output(&p);
exit(0);
}
}else free(p);
break;
case 2:p=(QueuePtr)malloc(sizeof(QNode));//祖先结点从下来 if(moveleft(closed->next->data,&p,closed->next->x,closed->next->y)){
p->father=closed->next;
p->ancent=1;
p->gone=0;
p->deep=min->deep+1;
p->value=getvalue(&p);
p->next=open.rear->next;
open.rear->next=p;
open.rear=p;
//比较输出结果
if(compare(p->data)){
output(&p);
exit(0);
}
}else free(p);
p=(QueuePtr)malloc(sizeof(QNode));
if(moveup(closed->next->data,&p,closed->next->x,closed->next->y)){
p->father=closed->next;
p->ancent=2;
p->gone=0;
p->deep=min->deep+1;
p->value=getvalue(&p);
p->next=open.rear->next;
open.rear->next=p;
open.rear=p;
//比较输出结果
if(compare(p->data)){
output(&p);
exit(0);
}
}else free(p);
p=(QueuePtr)malloc(sizeof(QNode));
if(moveright(closed->next->data,&p,closed->next->x,closed->next->y)){
p->father=closed->next;
p->ancent=4;
p->gone=0;
p->deep=min->deep+1;
p->value=getvalue(&p);
p->next=open.rear->next;
open.rear->next=p;
open.rear=p;
//比较输出结果
if(compare(p->data)){
output(&p);
exit(0);
}
}else free(p);
break;
case 3:p=(QueuePtr)malloc(sizeof(QNode));//祖先结点从上来 if(moveleft(closed->next->data,&p,closed->next->x,closed->next->y)){
p->father=closed->next;
p->ancent=1;
p->gone=0;
p->deep=min->deep+1;
p->value=getvalue(&p);
p->next=open.rear->next;
open.rear->next=p;
open.rear=p;
//比较输出结果
if(compare(p->data)){
output(&p);
exit(0);
}
}else free(p);
p=(QueuePtr)malloc(sizeof(QNode));
if(movedown(closed->next->data,&p,closed->next->x,closed->next->y)){
p->father=closed->next;
p->ancent=3;
p->gone=0;
p->deep=min->deep+1;
p->value=getvalue(&p);
p->next=open.rear->next;
open.rear->next=p;
open.rear=p;
//比较输出结果
if(compare(p->data)){
output(&p);
exit(0);
}
}else free(p);
p=(QueuePtr)malloc(sizeof(QNode));
if(moveright(closed->next->data,&p,closed->next->x,closed->next->y)){
p->father=closed->next;
p->ancent=4;
p->gone=0;
p->deep=min->deep+1;
p->value=getvalue(&p);
p->next=open.rear->next;
open.rear->next=p;
open.rear=p;
//比较输出结果
if(compare(p->data)){
output(&p);
exit(0);
}
}else free(p);
break;
case 4:p=(QueuePtr)malloc(sizeof(QNode));//祖先结点从左边来
if(moveup(closed->next->data,&p,closed->next->x,closed->next->y)){
p->father=closed->next;
p->ancent=2;
p->gone=0;
p->deep=min->deep+1;
p->value=getvalue(&p);
p->next=open.rear->next;
open.rear->next=p;
open.rear=p;
//比较输出结果
if(compare(p->data)){
output(&p);
exit(0);
}
}else free(p);
p=(QueuePtr)malloc(sizeof(QNode));
if(movedown(closed->next->data,&p,closed->next->x,closed->next->y)){
p->father=closed->next;
p->ancent=3;
p->gone=0;
p->deep=min->deep+1;
p->value=getvalue(&p);
p->next=open.rear->next;
open.rear->next=p;
open.rear=p;
if(compare(p->data)){ //比较输出结果
output(&p);
exit(0);
}
}else free(p);
p=(QueuePtr)malloc(sizeof(QNode));
if(moveright(closed->next->data,&p,closed->next->x,closed->next->y)){
p->father=closed->next;
p->ancent=4;
p->gone=0;
p->deep=min->deep+1;
p->value=getvalue(&p);
p->next=open.rear->next;
open.rear->next=p;
open.rear=p;
if(compare(p->data)){ //比较输出结果
output(&p);
exit(0);
}
}else free(p);
break;
default:p=(QueuePtr)malloc(sizeof(QNode));//初始情况
if(moveleft(closed->next->data,&p,closed->next->x,closed->next->y)){
p->father=closed->next;
p->ancent=1;
p->gone=0;
p->deep=min->deep+1;
p->value=getvalue(&p);
p->next=open.rear->next;
open.rear->next=p;
open.rear=p;
//比较输出结果
if(compare(p->data)){
output(&p);
exit(0);
}
}else free(p);
p=(QueuePtr)malloc(sizeof(QNode)); if(moveup(closed->next->data,&p,closed->next->x,closed->next->y)){
p->father=closed->next;
p->ancent=2;
p->gone=0;
p->deep=min->deep+1;
p->value=getvalue(&p);
p->next=open.rear->next;
open.rear->next=p;
open.rear=p;
if(compare(p->data)){ //比较输出结果
output(&p);
exit(0);
}
}else free(p);
p=(QueuePtr)malloc(sizeof(QNode)); if(movedown(closed->next->data,&p,closed->next->x,closed->next->y)){
p->father=closed->next;
p->ancent=3;
p->gone=0;
p->deep=min->deep+1;
p->value=getvalue(&p);
p->next=open.rear->next;
open.rear->next=p;
open.rear=p;
if(compare(p->data)){ //比较输出结果
output(&p);
exit(0);
}
}else free(p);
p=(QueuePtr)malloc(sizeof(QNode));
if(moveright(closed->next->data,&p,closed->next->x,closed->next->y)){
p->father=closed->next;
p->ancent=4;
p->gone=0;
p->deep=min->deep+1;
p->value=getvalue(&p);
p->next=open.rear->next;
open.rear->next=p;
open.rear=p;
if(compare(p->data)){ //比较输出结果
output(&p);
exit(0);
}
}else free(p);
break;
}
}printf("error: no answer!\n");
}