A*算法程序

我曾看过一些有关A*算法的程序,不过写得比较简洁、易懂的还是风云写的A*算法教学实例,但是这个算法并没有进行优化,该程序要用到实际应用中, 还会有一定的限制, 所以我对该算法进行了改进,并加上更详细的算法说明,使其具有更好的教学作用和实用价值。开始前我先给出A*算法的基本思路:
  问题:求出2D的迷宫中起始点S到目标点E的最短路径?
  算法:

  findpath()
  {
    把S点加入树根(各点所在的树的高度表示从S点到该点所走过的步数);
    把S点加入排序队列(按该点到E点的距离排序+走过的步数从小到大排序);
    1、排序队列sort_queue中距离最小的第一个点出列,并保存入store_queue
    2、从出列的点出发,分别向4个(或8个)方向中的一个各走出一步
    3、并估算第2步所走到位置到目标点的距离,并把该位置加入树,最后把该点按距离从小到大排序后并放入队列中(由trytile函数实现)
    4、如果该点从四个方向上都不能移动,则把该点从store_queue中删除
    5、回到第一点,直到找到E点则结束

    从目标点回溯树,直到树根则可以找到最佳路径,并保存在path[]
  }

  文末附带的程序参考了风云的最短路径代码,并加以改进和优化:

把原来用于存放已处理节点的堆栈改为队列(store_queue),这样在从sort_queue队列出列时可直接放入store_queue中。
解除了地图大小的限制(如果有64K内存限制时,地图大小只能是180x180)。
删除了原程序中的一些冗余,见程序中的注释。
程序继续使用dis_map数组保存各点历史历史最佳距离,也包含了某点是否已经经过的信息,虽然这样做可能会比使用链表多用一些内存,但是在搜索时可以节省不时间。
程 序更具有实用性,可直接或修改后运用于你的程序中,但请你使用该代码后 应该返回一些信息给我,如算法的改进或使用于什么程序等。 本程序可以用Borland C++DJGPP编译,并附带有一个数据文件,保存有地图的数据。最后值得一提的是,该地图文件格式与风云的源代码的地图格式不一样。
/*-------------------------------------------------------------------------*/

//#define NDEBUG
#include
#include
#include
#include

#define tile_num(x,y) ((y)*map_w+(x)) // x,y 坐标转换为地图上块的编号
#define tile_x(n) ((n)%map_w) //
由块编号得出 x,y 坐标
#define tile_y(n) ((n)/map_w)

#define MAPMAXSIZE 180 //地图面积最大为 180x180,如果没有64K内存限制可以更大
#define MAXINT 32767

//树结构, 比较特殊, 是从叶节点向根节点反向链接,方便从叶节点找到根节点
typedef struct tree_node *TREE;

struct tree_node {
int h; //
节点所在的高度,表示从起始点到该节点所有的步数
int tile; //
该节点的位置
TREE father; //
该节点的上一步
};

//链接结构,用于保存处理过的和没有处理过的结点
typedef struct link_node *LINK;

struct link_node {
TREE node;
int f;
LINK next;
};

LINK sort_queue; // 保存没有处理的行走方法的节点
LINK store_queue; //
保存已经处理过的节点 (搜索完后释放)

unsigned char * map; //地图数据
unsigned int * dis_map; //
保存搜索路径时,中间目标地最优解

int map_w,map_h; //地图宽和高
int start_x,start_y,end_x,end_y; //
地点,终点坐标

// 初始化队列
void init_queue()
{
sort_queue=(LINK)malloc(sizeof(*sort_queue));
sort_queue->node=NULL;
sort_queue->f=-1;
sort_queue->next=(LINK)malloc(sizeof(*sort_queue));
sort_queue->next->node=NULL;
sort_queue->next->f=MAXINT;
sort_queue->next->next=NULL;

store_queue=(LINK)malloc(sizeof(*store_queue));
store_queue->node=NULL;
store_queue->f=-1;
store_queue->next=NULL;
}

// 待处理节点入队列, 依靠对目的地估价距离插入排序
void enter_queue(TREE node,int f)
{
LINK p=sort_queue,father,q;
while(f>p->f) {
father=p;
p=p->next;
assert(p);
}
q=(LINK)malloc(sizeof(*q));
assert(sort_queue);
q->f=f,q->node=node,q->next=p;
father->next=q;
}

// 将离目的地估计最近的方案出队列
TREE get_from_queue()
{
LINK bestchoice=sort_queue->next;
LINK next=sort_queue->next->next;
sort_queue->next=next;

bestchoice->next=store_queue->next;
store_queue->next=bestchoice;
return bestchoice->node;
}

// 释放栈顶节点
void pop_stack()
{
LINK s=store_queue->next;
assert(s);
store_queue->next=store_queue->next->next;
free(s->node);
free(s);
}

// 释放申请过的所有节点
void freetree()
{
int i;
LINK p;
while(store_queue){
p=store_queue;
free(p->node);
store_queue=store_queue->next;
free(p);
}
while (sort_queue) {
p=sort_queue;
free(p->node);
sort_queue=sort_queue->next;
free(p);
}
}

// 估价函数,估价 x,y 到目的地的距离,估计值必须保证比实际值小
int judge(int x,int y)
{
int distance;
distance=abs(end_x-x)+abs(end_y-y);
return distance;
}

// 尝试下一步移动到 x,y 可行否
int trytile(int x,int y,TREE father)
{
TREE p=father;
int h;
if (map[tile_num(x,y)]!=' ') return 1; //
如果 (x,y) 处是障碍,失败
//
这一步用来判断(x,y)点是否已经加入队列,多余可以删除,因为dis_map已经
//
保存该点是否已经保存
//while (p) {
// if (x==tile_x(p->tile) && y==tile_y(p->tile)) return 1; //
如果 (x,y) 曾经经过,失败
// p=p->father;
//}
h=father->h+1;
if (h>=dis_map[tile_num(x,y)]) return 1; //
如果曾经有更好的方案移动到 (x,y) 失败
dis_map[tile_num(x,y)]=h; //
记录这次到 (x,y) 的距离为历史最佳距离

// 将这步方案记入待处理队列
p=(TREE)malloc(sizeof(*p));
p->father=father;
p->h=father->h+1;
p->tile=tile_num(x,y);
enter_queue(p,p->h+judge(x,y));
return 0;
}

// 路径寻找主函数
int * findpath(void)
{
TREE root;
int i,j;
int * path;
memset(dis_map,0xff,map_h*map_w*sizeof(*dis_map)); //
填充dis_map0XFF,表示各点未曾经过
init_queue();
root=(TREE)malloc(sizeof(*root));
root->tile=tile_num(start_x,start_y);
root->h=0;
root->father=NULL;
enter_queue(root,judge(start_x,start_y));
for (;;) {
int x,y,child;
TREE p;
root=get_from_queue();
if (root==NULL) {
return NULL;
}
x=tile_x(root->tile);
y=tile_y(root->tile);
if (x==end_x && y==end_y) break; //
达到目的地成功返回

child=trytile(x,y-1,root); //尝试向上移动
child&=trytile(x,y+1,root); //
尝试向下移动
child&=trytile(x-1,y,root); //
尝试向左移动
child&=trytile(x+1,y,root); //
尝试向右移动
//child&=trytile(x+1,y-1,root);//
尝试向右上移动
//child&=trytile(x+1,y+1,root); //
尝试向右下移动
//child&=trytile(x-1,y+1,root); //
尝试向左下移动
//child&=trytile(x-1,y-1,root); //
尝试向左上移动

if (child!=0)
pop_stack(); //
如果四个方向均不能移动,释放这个死节点
}

// 回溯树,将求出的最佳路径保存在 path[]
path=(int*)malloc((root->h+2)*sizeof(int));
assert(path);
for (i=0;root;i++) {
path[i]=root->tile;
root=root->father;
}
path[i]=-1;
freetree();
return path;
}

void printpath(int *path)
{
int i;
if(path==NULL) return ;
for (i=0;path[i]>=0;i++) {
gotoxy(tile_x(path[i])+1,tile_y(path[i])+1);
cprintf(".");
}
}

int readmap()
{
FILE *f;
int i,j;
f=fopen("map.dat","r");
assert(f);
fscanf(f,"%d,%d/n",&map_w,&map_h);
map=malloc(map_w*map_h+1);
assert(map);
for(i=0;i fgets(map+tile_num(0,i),map_w+2,f);
fclose(f);
start_x=-1,end_x=-1;
for (i=0;i for (j=0;j if (map[tile_num(j,i)]=='s') map[tile_num(j,i)]=' ',start_x=j,start_y=i;
if (map[tile_num(j,i)]=='e') map[tile_num(j,i)]=' ',end_x=j,end_y=i;
}
assert(start_x>=0 && end_x>=0);
dis_map=malloc(map_w*map_h*sizeof(*dis_map));
assert(dis_map);
return 0;
}

void showmap()
{
int i,j;
clrscr();
for (i=0;i gotoxy(1,i+1);
for (j=0;j if (map[tile_num(j,i)]!=' ') cprintf("O");
else cprintf(" ");
}
gotoxy(start_x+1,start_y+1);
cprintf("s");
gotoxy(end_x+1,end_y+1);
cprintf("e");
}

int main()
{
int * path;
readmap();
showmap();
getch();
path=findpath();
printpath(path);
if(dis_map) free(dis_map);
if(path) free(path);
if(map) free(map);
getch();
return 0;
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
八数码问题,多多指教 欢迎大家下载使用参考 1. 概述 1.1 8数码问题 8数码问题是指在3X3的九宫棋盘上有标号为1~8的8个棋牌,和一个空白位,通过棋牌向空白位移动来改变棋盘布局,如何移动棋牌才能从初使布局到达目标布局.显然解答路径实际上就是一个合法的走步序列 1.2 A*算法 A*算法属于一种启发式搜索,它扩展结点的次序类似于广度优先搜索,但不同的是每生成一个子结点需要计算估价函数F,以估算起始结点的约束经过该结点至达目标结点的最佳路径代价;每当扩展结点时,意是在所有待扩展结点中选择具有最小F值的结点做为扩展对象,以便使搜索尽量沿最有希望的方向进行.A*算法只要求产生问题的全部状态空间的部分结点及关系,就可以求解问题了,搜索效率较高。 1.3 A*算法的一般描述 1.3.1约定 S:指示初使状态节点(Note);G:指示搜索图 OPEN:作为存放待扩展节点的表;SNS:子节点集合 CLOSE:作为存放已被扩展节点的表 Move_First(Open):指示取Open表首节点作为当前要被扩展的节点n,同时将节点n移到CLOSE表; F(n) = G(n) + H(n):评价函数,用于排列节点在OPEN表中的位置 1.3.2算法过程 ○1 G:=S; ○2 OPEN := (S), CLOSE := (); ○3 如果OPEN为空则算法失败 ○4 n := Move_First(OPEN); ○5 如果n是目标结点,搜索完成。 ○6 扩展节点n,将非节点n祖先的子节点置于子点子集合SNS中,并插入G,对SNS每个子节点计算F(n,ni)=G(n,ni)+H(ni) ○7 标记与修改指针: 1.把SNS中的子结点分为三类:全新结点,已出现于OPEN表的结点,已出现于CLOSE表的节点;2.加第一类子节点于OPEN表;3.由评价函数值,选择最优结点,并移动子结点指向父节点的指针,改为指向新父节点 ○8 重新排序OPEN表中结点; ○9 返回语句○3 2. A*算法在VC6.0开发环境下的实现 1.类(可参见附件) 1.1 CDisplay类 由于结果以各种棋盘的布局来表示空白位的移动,因此建立这个类的目的是记录棋盘布局,并指出这个布局是否已经存在,是否正解,是否已扩展(已扩展并不表示是正解,正解一定表示已扩展).同时这个类的对象也是搜索树的节点 1.2 CMain类 这个类负责A*算法的全部过程,如:初值与结果的取得,空白位的移动,评价函数值的计算,选择当前最优移动方法,生成下一个棋盘布局,形成搜索树等 1.3 其它类 1.CAI3Doc类:VC6.0 AppWizard自动生成,在本程式中负责将CMain类对象的运算结果(搜索树)转换成可显示格式 2.CAI3View类:VC6.0 AppWizard自动生成,在本程序中负责将CAI3Doc类转换好的内容显示。 3. CMainFrame类:VC6.0AppWizard自动生成,在本程序中负责异常处理(搜索出错,运算成功等) 4.CAI3App类:VC6.0 AppWizard自动生成,本程序中无特殊使用 5.CPtrList类:在本程式中预定义为List,它的对象用于树(搜索树,结果显示树)的存储 6.CPoint类:在本程式中预定义为Position,它的对象用于表示坐标 2.数据结构: 在本程序中,棋盘布局以一个3X3的二元数组表示;CDisplay类的对象作为搜索树的节点,搜索树以链表形式存储(可参见附件) 2. 程序流程图与相关说明: 2.1 生成搜索树 注一: Op1 = Op2表示将Op2的内容全部复制到Op1中, 注二: 标志为解状态的原因是为了找到解图; 标志为某状态为解状态的同时也标记此状态为已扩展 注三:取得空白位的位置在Op2初使化时就已完成。 注四:由于G值对于子结点是相同的,因此只要比较H值就相当于比较了F值 3. 主要程序代码及注释 为节省篇幅,本文不加入源程序代码 请查看CMain.h,CMain.cpp,CDisplay.h,CDisplay.cpp 4. 其它 1.感谢张文亮提供算法(H值的计算)帮助 2.改变程序中MaxItem的设定和相关输入方式(主要指对话框),本程序可以用来计算Maxitem*MaxItem-1数码问题(本程序中MaxItem等于3) 3. 结束语 A*算法在运行过程中,也不可避免的用到了穷举(比如取得空白位的移动方式),但由于评价函数的存在使A*算法能够减少许多类似于穷举的工作。因此它只求解了问题的全部状态空间中的部分状态,提高了效率和减少了用于处理的空间。另外A*算法是AI的基本算法之一。我们也可以这么说:人工智能的精髓就是穷举,人工智能的关键就是控制策略
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值