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。這是他的博客。

如果有興趣的可以留言,一起交流一下算法學習的心得。

接下來我會發表介紹幾種搜索算法。

聲明:本文章是筆者整理資料所得原創文章,如轉載需註明出處,謝謝。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章