AI中的几种搜索算法---A*搜索算法

AI中的几种搜索算法---A*搜索算法

引言

A*搜索算法作为一种典型的启发式搜索(Informed Search)算法,这种算法与一般的算法相比,便是其在搜索过程中,会利用一些引导机制,来引导整个搜索。相比于盲目的搜索,其性能是不言而喻的。

而运用A*最广泛的地方便是游戏中的路径搜索(Path Finding)。这篇文章主要会基本地介绍A*算法,并会介绍一个游戏路径搜索的例子。

这里套着AI这个帽子,我会先介绍一些基本的搜索算法;随后我会写几篇文章介绍AI中几个经典概念:Machine Learning、基因算法等。

 

一、A*搜索算法的基本介绍

1.启发式搜索

启发式搜索,简单地说,便是在搜索下一个结点的时候,利用启发函数,能够找到搜索代价最小的结点,进行搜索。

比较有名的启发式搜索:BFS(Best First Search)、A * Search、SA、TABU search等等。

我这里只介绍A*,因为这个算法是其中名气最大的,也是比较典型的。其中BFS和A*很类似,所以不会单独写一篇文章进行介绍,不过我会在之后几种搜索算法综合比较的时候对其进行简单介绍;SA算法是我下篇讨论的对象。

2.核心等式

我将这个等式称为消耗公式,意思是说搜索的结点需要耗费的“资源”。

首先简单介绍一下这个等式:f(n) = g(n) + h(n)(公式1)。

其中g(n)表示消耗(cost),也可以这么理解g:从出发到现在的消耗,该值是从搜索空间中计算得到。

其中h表示引导函数(heuristic function),可以这么理解h:从现在到目标的距离,注意这里的距离是一个抽象概念,该值是一个预测值。

3.两个列表

然后介绍一下算法当中需要用到的两个列表:open list和closed list。

Open list是一个优先队列,为每一次搜索提供最好的结点。该队列以结点的f为升序,该队列是A*算法更有效率地找到从开始到目标最短的路径。

Closed list是一个普通队列,保存了已访问过的结点,避免一个结点的重复访问。

4.A*算法的流程

大致描述一下这个流程:

1.     首先便是初始化工作,初始化上面提到的两个列表(Open list和Closed list),并获取一个开始结点放入Open list中。

2.     然后进行具体的搜索,从优先队列中取出最好的搜索结点(记为cur_node),这里最好的搜索结点便是根据公式1计算出来的,拥有最小f的结点。由于open list是一个优先队列,所以能够很方便地取出最优结点。

3.     如果取出的cur_node就是目标,就是我们要的结果,那么就结束算法。

4.     将cur_node保存在closed list中。表示我们已经访问这个结点。

5.     展开cur_node所有的邻接结点,并进行遍历(adj_node)。

6.     如果adj_node 已存在于closed list。表示我们已经访问过adj_node。取下一个邻接结点,继续步骤6。

7.     如果结点已存在于open list中,并且cur_node比open list上的那个结点好,也就是所cur_node的g小于那个结点,那么计算一下cur_node的f,并进行替换。

8.     如果cur_node不存在于open list中,那么计算cur_node的f并插入open list。

下面便是一个伪代码的流程:

Initialize the Open list.
Initialize the Closed list.
Add the start node to the open list.
loop util the open list is empty
  get the best node(cur_node) from open list.
   ifcur_node is the goal node
     end the algorithm.
  end if
  add cur_node to closed list.
  get the adjacent nodes(adj_node) of cur_node.
     if adj_node is on the closed list
        continue.
     end if
     if adj_node is on the open list(openlist.adjnode)
        if adj_node is better than openlist.adjnode
           replace the openlist.adjnode with adj_node.
        end if
     else
         add adj_node to open list.
     end else
   end
end loop


这个流程可以简单的理解:搜索的时候不停地往两个list中插数据,open list保证每次访问的结点是其中最好的结点,而closed list则保证要访问的结点在之前没有被访问过的。

二、游戏中的路径搜索

这里举一个2D 迷宫寻出路的例子。

1.消耗公式

我们来简要的分析一下。我们就利用公式1来进行切入,首先我们要计算出h。

当前距离目标的“距离”,这个距离可以是当前位置与目标位置之间具体的物理距离()。也可以是一个抽象距离:比如说当前位置要去目标位置的消耗,你明白去两个地方的消耗,不光要考虑要距离,还更要考虑路况的。

当然为了讨论和实现的简单,我们只需考虑物理距离就行了。

既然我们已经得出h,那么接下来就考虑g,于h相类似,g的计算简单多了,已走路线的长度就行了。

这样整个具体的消耗公式便是:

(公式2)

2.代码

上面说了这么多,这里就放一段核心的代码,其中pqueue4path和queue4path这两个是我自己专门为这个例子写的,其中pqueue4path便是那个优先队列。

至于其中的CaculateCost,是计算结点的消耗,包括g、h和f。

PerturbPath这个函数,主要充当获得邻接结点的作用。

如果要看整个项目代码请于A*演示程序于此处下载下载。


int astart_pathsearch(constPos & posStart,const Pos & posEnd,int * map,int nWidth,int nHeight,Path & solution)
{
    Path path,path2;
    path.length = 1;
    path.positions = newPos[path.length];
    path.positions[0].x = posStart.x;
    path.positions[0].y = posStart.y;
    CaculateCost(path,posEnd);
    //
    pqueue4path openlist;
    queue4path closelist;
    openlist.enQueue(path);
    for(int depth = 1;!openlist.isEmpty();++depth)
    {
        openlist.deQueue(path);
        if(!path.costtoend)//find the solution
        {
            solution.length = path.length;
            solution.positions = new Pos[path.length];
            for(int i = 0 ; i < path.length;++i)
            {
                solution.positions[i].x =path.positions[i].x;
                solution.positions[i].y =path.positions[i].y;
            }
            delete[]path.positions;
            delete[]path2.positions;
            returndepth;
        }
        closelist.enQueue(path);
        for (int i = 1 ; i < 5 ; ++i)
        {
            if(!perturbPath(path,map,nWidth,nHeight,i,path2))
                continue;
            CaculateCost(path2,posEnd);
            path2.cost += depth*depth;
            if(closelist.isOnList(path2))
                continue;
            if(openlist.isOnList(path2))
            {
                if(path2.cost< path.cost)
                {
                    openlist.deletePath(path2);
                    openlist.enQueue(path2);
                }
            }
            else
            {
                openlist.enQueue(path2);
            }
        }
    }
    delete[]path.positions;
    delete[]path2.positions;
    return -1;
}


3.寻路效果图


三、总结

这是我第一次写关于算法的文章,难免会有一些错误和不恰当的地方,这里就请读者多多包涵。

这篇文章中提到的一些代码,可以去我的CSDN资源那边下载,我专门写了一个迷宫寻路的小程序。至于这个程序,由于写比较急,所以也没有好好的测试。如果有什么bug或者算法有写错的地方,请好心的读者指出。我也能对此进行改进,并写出更有益的文章。

写这篇文章主要受一位大牛博主的启发http://blog.csdn.net/v_JULY_v。这是他的博客。

如果有兴趣的可以留言,一起交流一下算法学习的心得。

接下来我会发表介绍几种搜索算法。

声明:本文章是笔者整理资料所得原创文章,如转载需注明出处,谢谢。

 

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页