回 帖 发 新 帖 刷新版面

主题:论“A*”算法(原创) 请高手点评

    “A*”算法就是“启发式搜索法”,在很多游戏中都有使用,我们就来讨论一下它的原理和具体算法。
    穷举法大家应该都知道吧,它把问题的所有可能答案都一一举出,然后再比较一下与条件是否吻合。它几乎能解决所有问题,但显然效率不高,因为有些答案一开始就不可能成立。所以我们需要一种更高效的方法。
    由此产生了“广度搜索法”(好象是这个名字)。它的基本思想是:从以知条件开始,根据一定的规则,产生几个新的的节点(可能答案),判断是否是真正的答案,如果不是,分别以这几个新节点为起点按前面的规则再产生新节点,直到的出答案。如果需要输出求解过程,用反向跟踪即可。
    这段话不太好懂,下面举一个例子:
    有 5 盏亮着的灯,每次只能且必须操作其中 3 盏(既亮的熄灭,熄灭的变亮),问把它们全部熄灭至少要操作多少次?
    每盏灯只有熄灭和亮两种状态,我们用“O”表示亮,用“X”表示熄灭,根据“广度搜索法”可得出下面的“树”:
1.          OOOOO(初始状态)
              |
2.          XXXOO (为什么是"XXXOO",而不是"OXXXO"? 其实它们在本质上是相同的,都有2盏灯亮着)
           /     \
3.    XOOXO       XXOXX
     /     \     /     \
4.XXXXX OXOOO OOOOX OOXXX
(答案)

    我们慢慢分析。从“OOOOO”也就是这棵树的第一层,跟据“只能且必须操作其中 3 盏”的条件,产生了第二层“XXXOO”,但显然它不是最终答案,于是再产生下一层。到了第三层,仍然没有。好吧,我再产生(生产?),第四层,哈哈找到啦。
    看了上面的叙述,你应该对“广度搜索法”有大致的了解了吧?(什么?!不懂?晕~)
    下面讲讲具体如何实施。
    “广度搜索法”使用了一种“先进后出”的队列式的数据结构。数组a()保存队列数据,变量h指向队头,i指向队尾,数组f()用来标明对应节点的父节点(如果需要的话)。具体过程如下:

1.从i指向的节点开始产生新节点
2.检查新节点是否重复,如果重复,跳回1.
3.e加一,在e所指位置上保存新节点,如果是答案则输出结果。
4.如果i节点能产生的节点未全部产生,则跳回1.
5.i加一,跳回1.

上面的问题可用下面的程序求解:(QBASIC)

'初始化
CLS
DIM a(100) AS STRING, f(100) AS INTEGER, h AS INTEGER, i AS INTEGER
DIM j AS INTEGER, k AS INTEGER, l AS INTEGER, t AS STRING, s AS INTEGER
DIM k1 AS INTEGER, k2 AS INTEGER, w(100) AS STRING
a(0) = "OOOOO"
f(0) = -1
h = 0
i = 0

DO
    FOR j = 0 TO 3
'产生新节点,其中k1代表应关闭的灯的个数,k2代表应打开灯的个数。
        t = a(h)
        k1 = j
        k2 = 3 - j
        l = 1
        DO
            IF MID$(t, l, 1) = "O" THEN
                IF k1 > 0 THEN MID$(t, l, 1) = "X": k1 = k1 - 1
            ELSE
                IF k2 > 0 THEN MID$(t, l, 1) = "O": k2 = k2 - 1
            END IF
            l = l + 1
        LOOP UNTIL l > 5
'检查是否重复
        FOR l = 0 TO i
            IF a(l) = t THEN EXIT FOR
        NEXT l
'保存节点
        IF k1 = 0 AND k2 = 0 AND l > i THEN
            i = i + 1
            a(i) = t
            f(i) = h
        END IF
'如果是答案则输出结果
        IF a(i) = "XXXXX" THEN
            l = 0
'“反向跟踪”整理数据
            DO
                w(l) = a(i)
                i = f(i)
                l = l + 1
            LOOP UNTIL i = -1
            FOR i = l - 1 TO 0 STEP -1
                PRINT w(i)
            NEXT i
            END
        END IF
            
    NEXT j
    h = h + 1
LOOP

    “广度搜索法”大大提高了搜索效率,而“启发式搜索法”更厉害。其实“启发式搜索法”只是比“广度搜索法”多了一个“估价函数”。“估价函数”应能对当前节点与答案的“距离”做出估算,“距离”小的节点优先处理。“估价函数”的具体形式视所处理问题而定。

    算法过程如下:
与“广度搜索法”类似,数组a()保存队列数据,变量h指向队头,i指向队尾,数组f()用来标明对应节点的父节点,s()记录估计“距离”。

1.从i指向的节点开始产生新节点
2.检查新节点是否重复,如果重复,跳回1.
3.e加一,在e所指位置上保存新节点,如果是答案则输出结果。
4.如果i节点能产生的节点未全部产生,则跳回1.
5.调用“估价函数”,把结果一一存入s()中,再跟据s()把队列按从小到大的顺序排列。
5.i加一,跳回1.

    我举个例子来讲解吧。
    路径搜索的问题。有一张矩形地图被分成m*n个正方形,有的正放形可以直接通过,有的则是障碍物。已知起点和终点,只能向上、下、左、右走,不能斜着走,寻找从起点到终点的最短路径。
    (好累啊)我就讲讲要点,剩下自己解决。
    这里的“估价函数”=|当前x轴坐标-目标x轴坐标| + |当前y轴坐标-目标y轴坐标| + 当前节点所处层数。
    有谁想到具体程序别忘了贴上来,有大奖(骗人的)。

回复列表 (共6个回复)

沙发

'说明 费了九牛 二虎之力终于做成了一个vb6.0版的 A* 算法,请大虾们指教
'由于这个论坛不能粘贴附件所以附带的原程序(一个测试用的游戏)没有发上来
'  ---->智能搜索模块  用于在游戏中 自动寻找 最近的道路
'-----------作者 张林   www.163.tom.com
'用于存贮 用findpath 找到的坐标
'Public Map1() As Integer                'Our map array 地图数组
'Public MapSizeX As Integer
'Public MapSizeY As Integer
Public Const MaxtNum = 600
Public Const Linewidth = 19
Public Const lineheighter = 14
Public Const 一行的单元数量 = (Linewidth + 1)

Public Type Node
     h As Integer '当前点到目标点的距离
     f As Integer '估价距离
    g As Integer '单前点离开其始点的距离
    x As Integer '单元的数组坐标
     y As Integer
     NodeNum As Integer '接点号码
     Parent As Integer '父接点
     Nextnode  As Integer '下一个接点的号
     Nownode As Integer  ''当前的接点号
     ForwardNum  As Integer 'closed表中的前一个接点号
End Type
Type 坐标
x As Integer
y As Integer
End Type

'Public Map(200, 150) As Integer '地图
Private Opened As STACK '打开的列表
Private Closed As STACK '关闭的列表
Private Node1() As Node
  '接点
Public 坐标组XY(0 To 600) As 坐标
Type STACK
   
     NextStackPtr As Integer
End Type





Public Function FindPath(sx As Integer, sy As Integer, dx As Integer, dy As Integer) As Node
'寻路 主函数
  ReDim Node1(0 To MaxtNum)
  Dim i As Integer
  Dim BestNode As Node
    Dim TileNumDest As Integer

    TileNumDest = tilenum(dx, dy)

  
Node1(0).g = 0
Node1(0).h = Abs(sx - dx) + Abs(sy - dy)
  Node1(0).f = Node1(0).g + Node1(0).h
  Node1(0).NodeNum = tilenum(sx, sy)
  Node1(0).Nownode = 0
  Node1(0).Parent = -1
   Node1(0).x = sx
   Node1(0).y = sy
   Node1(0).Nextnode = -1
    Opened.NextStackPtr = Node1(0).Nownode
    Closed.NextStackPtr = -1
    
    For i = 1 To (MaxtNum / 6)
    If Opened.NextStackPtr = -1 Then Exit For
    BestNode = ReturnBestNode() '  //取出最近的节点
    
    If (BestNode.NodeNum = TileNumDest) Then     '/* if we've found the end, break and finish */
     FindPath = BestNode
    Exit Function
   End If
    GenerateSuccessors BestNode, dx, dy
    Next i
'此处还没做好
FindPath = BestNode
End Function
Private Function tilenum(dx As Integer, dy As Integer) As Integer
tilenum = dy * 一行的单元数量 + dx
End Function
Private Function ReturnBestNode() As Node
' 从opened表中 选出 最佳接点
Dim tmp As Integer

If Opened.NextStackPtr = -1 Then
     ' MsgBox "ERROR: no more 接点 on OPEN"
    Debug.Print "错误: no more 接点 on OPEN"
      
   
End If
    tmp = Opened.NextStackPtr  ';   // point to first node on OPEN
    Opened.NextStackPtr = Node1(tmp).Nextnode  '  //从open列表里面取处一个 接点 open 直向 下下一个节点 Make OPEN point to nextnode or NULL.

Node1(tmp).Nextnode = Closed.NextStackPtr
Closed.NextStackPtr = tmp
ReturnBestNode = Node1(tmp)
End Function

Private Function GenerateSuccessors(BestNode As Node, dx As Integer, dy As Integer)
'按 4个方向检测 障碍物   如果是道路就 生成 新接点
Dim x As Integer
Dim y As Integer
    '上
    If FreeTile(BestNode.x, BestNode.y - 1) Then
     x = BestNode.x: y = BestNode.y - 1
      GenerateSucc BestNode, x, y, dx, dy
        End If
      '右
    If FreeTile(BestNode.x + 1, BestNode.y) Then
     x = BestNode.x + 1: y = BestNode.y
      GenerateSucc BestNode, x, y, dx, dy
  End If
     '下
     If FreeTile(BestNode.x, BestNode.y + 1) Then
     x = BestNode.x: y = BestNode.y + 1
      GenerateSucc BestNode, x, y, dx, dy
        End If
      '左
      If FreeTile(BestNode.x - 1, BestNode.y) Then
     x = BestNode.x - 1: y = BestNode.y
  
      GenerateSucc BestNode, x, y, dx, dy
         End If
End Function
Public Function FreeTile(x As Integer, y As Integer) As Boolean
If x < 0 Or y < 0 Or x > Linewidth Or y > lineheighter Then

FreeTile = False
Exit Function
End If
'If Map1(x, y) <> 1 Then FreeTile = False
'If Map1(x, y) = 1 Then FreeTile = True
If arena(3).layer(x, y).x = 999 Then  ' layer(x,y)里的 x是指的游戏地图里的数组坐标     layer(x,y).x 是指的 图片上的地图 的 x坐标
            'layer(x,y).x=999表示是 道路,其他的数值不能走
            FreeTile = True
            Else
            FreeTile = False
            End If
End Function
Private Sub GenerateSucc(BestNode As Node, x As Integer, y As Integer, dx As Integer, dy As Integer)


    Dim g As Integer
    Dim TileNumS As Integer
    Dim c As Integer
    c = 0
Dim old As Node
    g = BestNode.g + 1
TileNumS = tilenum(x, y)
   '在没在 opened列表
    old = CheckOPEN(TileNumS)
If old.NodeNum <> -1000 Then
    
    If g < old.g Then
       old.Parent = BestNode.Nownode
        old.g = g
        old.f = g + old.h
                  ' //还需要更新open列表的
                  
     End If
    
  Exit Sub
  End If
   '检查 closed列表
   old = CheckCLOSED(TileNumS)
    If old.NodeNum <> -1000 Then

        
    If g < old.g Then
        old.Parent = BestNode.Nownode
        old.g = g
        old.f = g + old.h
      '从closed 中 移出,放入opened 列表
      PropagateDown old
    End If
        
        
    
    
      Exit Sub
    
    End If
  '没有在 opened 和colsed 表中 就生成 新的接点
  For i = 0 To (MaxtNum)
  If Node1(i).x = 0 And Node1(i).y = 0 And Node1(i).g = 0 And Node1(i).h = 0 Then Exit For
  Next
   If i > MaxtNum Then Exit Sub
   Node1(i).NodeNum = TileNumS
   Node1(i).y = y
   Node1(i).x = x
   Node1(i).h = Abs(x - dx) + Abs(y - dy)
   Node1(i).g = g
   Node1(i).Parent = BestNode.Nownode
   Node1(i).f = g + Node1(i).h
   Node1(i).Nownode = i
   Insert Node1(i)

End Sub
Private Function CheckOPEN(tilenum As Integer) As Node
'检查是否在 opened表中
Dim INt父接点 As Integer
Dim 没有在Open列表 As Node
Dim tmp As Integer
    
    tmp = Opened.NextStackPtr
    INt父接点 = tmp
    While (tmp <> -1)
     If Node1(tmp).NodeNum = tilenum Then
     Node1(tmp).ForwardNum = INt父接点
     CheckOPEN = Node1(tmp)
   Exit Function
    Else
   INt父接点 = tmp
   tmp = Node1(tmp).Nextnode
  
  End If
Wend
没有在Open列表.NodeNum = -1000
CheckOPEN = 没有在Open列表
End Function

Private Function CheckCLOSED(tilenum As Integer) As Node
'检查是否在 closed表中
Dim ForwardNode As Integer

Dim 没有在Closed列表 As Node
Dim tmp As Integer
    tmp = Closed.NextStackPtr
    ForwardNode = tmp
    While (tmp <> -1)
     If Node1(tmp).NodeNum = tilenum Then
         
       Node1(tmp).ForwardNum = ForwardNode
       CheckCLOSED = Node1(tmp)
    Exit Function
    Else
    ForwardNode = tmp
   tmp = Node1(tmp).Nextnode
    End If
Wend
没有在Closed列表.NodeNum = -1000
CheckCLOSED = 没有在Closed列表


End Function
Private Sub PropagateDown(old As Node)
  '要生成的接点 在 colosed表中 并且 g 直 比 原来的小,把原来的 接点 重新修改 g直 和f直和 父接点  从closed 中 移出,放入opened 列表,这个子程序 好象没必要
If Node1(old.ForwardNum).Nextnode = old.Nextnode Then
Closed.NextStackPtr = old.Nextnode
Else
Node1(old.ForwardNum).Nextnode = old.Nextnode
End If
Insert old




End Sub
Private Sub SortOpened(old As Node)
If Node1(old.ForwardNum).Nextnode = old.Nextnode Then Exit Sub
Node1(old.ForwardNum).Nextnode = old.Nextnode
Insert old
End Sub
Private Sub Insert(Successor As Node)


'把新剩成的接点 排序后 插入 open列表
    Dim tmp1 As Node
    Dim temp2 As Integer
    Dim f, dd As Integer

      If Opened.NextStackPtr = -1 Then
      Opened.NextStackPtr = Successor.Nownode
      Successor.Nextnode = -1
    Exit Sub
    End If
f = Successor.f
  temp2 = Opened.NextStackPtr
    
    While (temp2 <> -1) And (Node1(temp2).f) < f
     dd = temp2
    temp2 = Node1(temp2).Nextnode
    If temp2 = -1 Then GoTo Gogo:
    
    Wend
Gogo:
    Successor.Nextnode = temp2
  If Opened.NextStackPtr = temp2 Then
  Opened.NextStackPtr = Successor.Nownode
   Else
   Node1(dd).Nextnode = Successor.Nownode
End If

End Sub
Public Sub GetPath(坐标组XY() As 坐标, Noddd As Node, ll As Integer)
ll = 0
'坐标组XY(ll).x = Noddd.x
'坐标组XY(ll).y = Noddd.y
tmp = Noddd.Nownode
While tmp > -1
坐标组XY(ll).x = Node1(tmp).x
坐标组XY(ll).y = Node1(tmp).y
tmp = Node1(tmp).Parent
If tmp = -1 Then Exit Sub
ll = ll + 1
Wend
End Sub

板凳

change "广度搜索法" to "广度优先搜索"
" '广度搜索法'大大提高了搜索效率 " 好像不对吧,BFS是一种盲目搜索的方法,搜索效率并不高。

3 楼

要看实际问题而定。

4 楼

真的是帅呆了,偶的偶像啊!!晕了

5 楼

哇。。。晕

6 楼

以下是我用c语言写的(但不知道是不是您说的那种)
该程序不知道哪儿错了 请高手帮一把! 小弟在此先谢过了!
因为才学编程 程序难免烦乱 垃圾代码也多 请您耐心看 好吗?
#include <stdio.h>
#include <malloc.h>
#include <math.h>
#define NULL 0
public int a[21][21]               //地图数组
int desert=60;                    //目标接点
int start=5;                     //开始接点
int cishu;                       //用于存储失败接点的个数

struct node  
{struct node *father1;            //它仅仅是在open 表里面排序用
struct node *father;              //它是指生成它的父接点
int val;                          //估价值
int place;                        //目前所在位置
struct node *next;                //下一个接点
}

struct node *open_p;                 //后缀加P的node*变量表示全局变量
struct node *close_p;
struct node *open_startp;
struct node *open_endp;
struct node *close_endp;

struct node *insert_open (struct node *father1)
{struct node *p1;
p1=(struct node *)malloc(sizeof(struct node));
p1->father=father1->father;
p1->val=father1->val;
p1->place=father1->place;
p1->next=NULL;
p1->father1=father->mother;
open_endp=p1;                      //将open的末指针指向生成的接点
return p1;                           //返回open表的最后一个接点的指针
}
struct node *insert_close (struct node *father1)
{struct node *p1;
p1=(struct node *)malloc(sizeof(struct node));
p1->father1=father1->mather;
p1->father=father1->father;
p1->val=father1->val;
p1->place=father->place;
p1->next=null;
close_endp=p1;                     //将close 表的末指针指向生成的接点
return p1;                         //返回close 表的最后一个接点的指针
}

struct node *res_open(int choice,struct node *open_start)
{
  while (open_start!=null)
  {
    if (choice==open_start->place)
      return open_start;         //返回找到open表里面找到接点的指针
    open_start=open_start->next;
   }
    return NULL;     //0 表示未找到
}

struct node *res_close(int choice,struct node *close_start)
{
while (close_start!=null)
  {
    if (choice==close_start->place)
      return close_start;        //返回找到close表里面找到接点的指针
    close_start=close_start->next;
   }
    return NULL;     //0 表示未找到

}

get_out (struct node *open_end, struct node *close_end)  //最优秀的接点出表
{int choice;
struct node *temp;
struct node *new_open;
struct node *new_close;
new_open=open_end->father1;
new_close=open_end;

while (open_end1!=0)
{
shu=open_end->place;
temp=open_end->father1;
close_end ->next =open_end1;
open_end =temp;
open_end->father=close_end;
(temp->father1)->next=NULL;
open_endp=new_open;
close_endp =new_close;
return choice;
}
open_endp=new_open;
close_endp =new_close;
return NULL ;            //0 表示链表为空,可以回朔;
}

val(int place,int desert)              //估价函数
{int x1,y1;
int x2,y2;
x1=place/20;
y1=place-(place/20)*20;
x2=desert/20;
y2=desert-(desert/20)*20;
if (a[i][j]!=1)
return fabs(x1-x2)+fabs(y1-y2);               
else return 4e3;
}

void goback(struct node *p)  //回朔程序
{int x;
int y;
int i;
int j;
for (, p!=NULL,);
{x=(p->place)/20;
y=20-((p->place)/20)*20;
a[x][y]=2;
p=p->father;
}
for(i=1;i<=20;i++)                   //打印数组(并且用 — * 作出区别)
{for (j=1; j<=20;j++)
{if (a[i][j]==0) printf(" ");        //可走动的
  else if (a[i][j]==1) printf("-");    //障碍
   else if (a[i][j]==2) printf("*");  //表示它在最短路径里
}
printf("\n");
}
exit(0);  //运算完毕 退出
}


//   主程序
main()
{
struct node *start_s;
struct node *begin_s;
struct node *temp_node;
struct node *j[5];
int choice;
int child1;
int child2;
int child3;
int child4;
struct node *k[5];    //在open 表里查找的结果
struct node *n[5];      //在close 表里查找的结果
int i;
int j;
for (i=1;i<=20;i++)         //初始化地图              避免产生数组越界的情况 使地图四周全为不可走
{a[i][1]=1;
  a[1][j]=1;
   a[i][20]=1;
   a[20][i]=1;}


start_s=(struct node *) malloc(sizeof(struct node));       //初始化open 表
open_startp=start_s;
open_startp->place=start;
open_startp->val=val(open_startp->place,desert);
open_startp->father1=NULL;
open_startp->father=NULL;
open_startp->next=NULL;
begin_s=(struct node *) malloc(sizeof(struct node));            //初始化close 表
close_startp=begin_s;
close_startp->place=NULL;
close_startp->val=NULL;
close_startp->father1=NULL;
close_startp->father=NULL;
close_startp->next=NULL;

loop1:
cishu=0;
choice=getout(open_endp,close_endp );
if (choice==NULL)
{goback(open_endp);   //如果扩展出来的都是已经走过 就以最后扩出来点接点做终点 返回
}
child1=choice-1;       //左
child2=choice +20;      //下
child3=choice +1;        //右
child4=choice -20;       //上
k[1]=res_open(choice1,open_startp);
n[1]=res_close(choice1,close_startp);
if (k[1]==NULL)
{cishu=cishu+1;
     if (n[1]==NULL)
{
//struct node *j[1];
j[1]=(struct node *)malloc (sizeof(struct node));
j[1]->place=child1;
j[1]->val=val(choice1);
j[1]->father=choice;
}
else
{ //struct node *k[1];
j[1]=k[1];
}
}

else
{
//struct node *j[1];
j[1]=(struct node *)malloc (sizeof(struct node));
j[1]->place=child1;
j[1]->val=4e3;
j[1]->father=choice;
}




k[2]=res_open( choice2,open_startp);
n[2]=res_close(choice2,close_startp);
if (k[2]==NULL)
{if (n[2]==NULL)
{
//struct node *j[2];
j[2]=(struct node *)malloc (sizeof(struct node));
j[2]->place=child2;
j[2]->val=val(choice2);
j[2]->father=choice;
}
else
{// struct node *k2;
j[2]=k[2];

}
}

else
{
//struct node *j[2];
j[2]=(struct node *)malloc (sizeof(struct node));
j[2]->place=child2;
j[2]->val=4e3;
j[2]->father=choice;
}



k[3]=res_open( choice3,open_startp);
n[3]=res_close(choice3,close_startp);
if (k[3]==NULL)
{if (n[3]==NULL)
{
//struct node *j[3];
j[3]=(struct node *)malloc (sizeof(struct node));
j[3]->place=child3;
j[3]->val=val(choice3);
j[3]->father=choice;
}
  else
  { //struct node *k3;
   j[3]=k[3];
  }
}
else
{
//struct node *j[3];
j[3]=(struct node *)malloc (sizeof(struct node));
j[3]->place=child3;
j[3]->val=4e3;
j[3]->father=choice;
}


k[4]=res_open( choice4,open_startp);
n[4]=res_close(choice4,close_startp);
if (k[4]==NULL)
{if (n[4]==NULL)
{
//struct node *j[4];
j[4]=(struct node *)malloc (sizeof(struct node));
j[4]->place=child1;
j[4]->val=val(choice4);
j[4]->father=choice;
}
else
{ //struct node *k[4];
j[4]=k[4];
}
}

else
{
//struct node *j[4];
j[4]=(struct node *)malloc(sizeof(struct node));
j[4]->place=child4;
j[4]->val=4e3;
j[4]->father=choice;
}

for (i=1;i<=4;i++)
{if (n[i]!=NULL)
  cishu=cishu+1;
}


if (cishu==4)
goback(open_endp);

for (i=1;i<=4,i++)              // 将接点排序  
{ for (j=2;j<=4;j++)
{ if (j[i]->val >= j[j]->val)
{temp_node=j[i];
  j[i]=j[j];
  j[j]=temp_node;
}
}
}
for (i=1;i<=4;i++)
{if (j[i]->val!=4e3)
  open_startp=insert_open(j[i]->father1,open_startp);
  }
for  (i=1;i<=4;i++)  // 如果终点就在扩展出的接点中 可以回朔
{if (j[i]->val==0)
goback(j[i]);
}      
goto loop1;            //未找到终点 从新扩展

}

我来回复

您尚未登录,请登录后再回复。点此登录或注册