A*啓發式搜索基礎

A*啓發式搜索基礎

傳統的搜索方式是盲目搜索,即到每一步的時候並沒有對每種情況進行有效的區分,這樣的結果是浪費了大量的時間,對很多沒有必要的數據進行了搜索。
而A*算法則在搜索的過程中會選取認爲“最優”的狀態進行搜索,而這正是這種算法的精華部分。

其實我們可以將他和Dijkstra進行一定的對比,他們的共同點很多,都是每次選取最優的點開始搜索,但是他們的“最優”策略不同也就決定了不同的效率。

  • Dijkstra是每次選取距離“出發點”最近的點開始搜索
  • A*算法則是選取距離“目標點”估計值最小的點開始搜索

注意這裏的估計值就是A*算法的核心部分了,本博文最後會介紹和Dijkstra的具體效率的區別。

A*算法將點到重點的距離分爲兩個部分:

F(n)=G(n)+H(n)

介紹:

  • F(n) 表示起點到目標的估計距離
  • G(n) 表示起點到n點的準確距離
  • H(n) 表示n點到目標點的估計距離

我們先假設從n 點到目標點的準確距離爲H(n) ,當然H(n) 的準確值我們並不知道,否則我們就不用費力氣去求了。
這裏H(n) 需要滿足的條件是

  • H(n)<=H(n)

需要說明的是,我們A*搜索的核心就是H(n) 的構建,最後構建的好壞直接決定了我們搜索的效率,我們的好是指:H(n) 越貼近H(n) 越好,但是注意一定不能超過實際H(n) 的值。

爲了和大多數的講解名字對應,本博文采用相同名字,即名openlist的優先隊列來存放需要搜索的內容,具體搜索步驟如下

  • 預定義H(n)=|beginxendx|+|beginyendy|
  • 預定義F(n)=G(n)+H(n)
  • openlist中F(n) 值小的優先級別高

    1. 將起點加入openlist中
    2. 從隊列中彈出元素
    3. 對彈出元素的周邊進行搜索,如果發現有未被搜索到的位置,並且不是牆,河水,峽谷等不能行走的位置,則將其加入到隊列中,並將其指向彈出元素。
      如果發現有已經被搜索過的位置,則查看其F(n) 值是否比現在走到那個位置的F(n) 值小,若現在是更優解,則更新其F(n) 值。如果其不在隊列中則把其重新加入隊列,否則什麼也不用做。
    4. 重複2-4步,直到找到目標點
    5. 最小路徑即目標點的F(n) 的值,同時根據維護的指針就可把最優路徑推出來

補充:

我們從F(n)=G(n)+H(n) 中可以推導出

  • G(n)=0 時,算法相當與bfs;
  • H(n)=0 時,算法相當於dfs;

自己寫了一個示例代碼如下:

@Frosero
#include <iostream>
#include <queue>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#define INF 0x3f3f3f3f
using namespace std;


char mps[12][21] = {                              //x*
                    {"...................."},     //0
                    {"...S................"},     //1
                    {"...................."},     //2
                    {"...................."},     //3
                    {"....##########......"},     //4
                    {"...................."},     //5
                    {"...................."},     //6
                    {"...................."},     //7
                    {".............#......"},     //8
                    {".............#..P..."},     //9
                    {".............#......"},     //10
                    {"...................."},     //11
};              //原地圖

const int beginx = 1,beginy = 3,endx = 9,endy = 16;
int link[12][21] = {0};           //1-up 2-right 3-down 4-left
int dx[] = {1,-1,0,0},dy[] = {0,0,1,-1},f[12][21]; //f用於儲存F值

bool can_move(int x,int y){
    return x >= 0 && x < 12 && y >= 0 && y < 20 && mps[x][y] != '#';
}

class Node{
public:
    int x,y;
    int f,g,h;
    Node() = default;
    Node(int X,int Y,int G):x(X),y(Y),g(G) {
        this->h = abs(x - endx) + abs(y - endy) ; //計算H(n)
        this->f = this->g + this->h;
    }
    bool operator < (const Node &rhs) const {
        return this->f > rhs.f;
    }
};

void A_bfs(){
    for(int i=0;i<12;i++) for(int j=0;j<21;j++) f[i][j] = INF;
    priority_queue<Node> openlist;
    while(!openlist.empty()) openlist.pop();
    openlist.push(Node(beginx,beginy,0));
    f[beginx][beginy] = abs(beginx - endx) + abs(beginy - endy) ;
    while(!openlist.empty()){
        Node now = openlist.top(); openlist.pop();
        if(now.x == endx && now.y == endy) break;
        if(now.f > f[now.x][now.y]) continue;  //如果發現剛纔記錄的F值比現在實際F值小
                                            //說明該點已經被更新過,忽略即可
        for(int i=0;i<4;i++){
            int nx = now.x + dx[i];
            int ny = now.y + dy[i];
            if(can_move(nx,ny) && now.g + 1 + abs(nx - endx) + abs(ny - endy) < f[nx][ny]){
                mps[nx][ny] = mps[nx][ny] == 'P' ? 'P' : '*' ;
                f[nx][ny] = now.g + 1 + abs(nx - endx) + abs(ny - endy) ;
                if(nx == now.x + 1) link[nx][ny] = 1;
                else if(nx == now.x - 1) link[nx][ny] = 3;
                else if(ny == now.y + 1) link[nx][ny] = 4;
                else if(ny == now.y - 1) link[nx][ny] = 2;   //標記指針數組
                openlist.push(Node(nx,ny,now.g + 1));
            }
        }
    }
}


int main(){
    A_bfs();
    int x = endx,y = endy;
    while(1){
        if(link[x][y] == 1) x--;
        else if(link[x][y] == 2) y++;
        else if(link[x][y] == 3) x++;
        else if(link[x][y] == 4) y--;
        if(x == beginx && y == beginy) break;
        mps[x][y] = '+';
    }

    printf("ans is : %d\n",f[endx][endy]);
    for(int i=0;i<12;i++,printf("\n")){
        for(int j=0;j<20;j++) printf("%c",mps[i][j]);
    }

    return 0;
}

/*

輸出:

ans is : 21
...*****............
..*S************....
..*+*************...
..*+************....
..*+##########***...
..*+++++++++++++**..
..*************+**..
...************+**..
....*********#*+**..
.....********#*+P...
......*.*...*#.*....
....................


符號解釋
S:起點
P:目標點
*:搜索過的路徑
+:搜索過的最優路徑
#:牆

Process returned 0 (0x0)   execution time : 0.362 s
Press any key to continue.



*/

作爲比較,我們用Dijkstra再做一次結果:

@Frosero
#include <iostream>
#include <queue>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#define INF 0x3f3f3f3f
using namespace std;


char mps[12][21] = {                              //x*
                    {"...................."},     //0
                    {"...S................"},     //1
                    {"...................."},     //2
                    {"...................."},     //3
                    {"....##########......"},     //4
                    {"...................."},     //5
                    {"...................."},     //6
                    {"...................."},     //7
                    {".............#......"},     //8
                    {".............#..P..."},     //9
                    {".............#......"},     //10
                    {"...................."},     //11
};

const int beginx = 1,beginy = 3,endx = 9,endy = 16;
int link[12][21] = {0};           //1-up 2-right 3-down 4-left
int dx[] = {1,-1,0,0},dy[] = {0,0,1,-1},f[12][21];

bool can_move(int x,int y){
    return x >= 0 && x < 12 && y >= 0 && y < 20 && mps[x][y] != '#';
}

class Node{
public:
    int x,y;
    int g;
    Node() = default;
    Node(int X,int Y,int G):x(X),y(Y),g(G) { }
    bool operator < (const Node &rhs) const {
        return this->g > rhs.g;
    }
};

void A_bfs(){
    for(int i=0;i<12;i++) for(int j=0;j<21;j++) f[i][j] = INF;
    priority_queue<Node> openlist;
    while(!openlist.empty()) openlist.pop();
    openlist.push(Node(beginx,beginy,0));
    f[beginx][beginy] = 0 ;
    while(!openlist.empty()){
        Node now = openlist.top(); openlist.pop();
        if(now.x == endx && now.y == endy) break;
        for(int i=0;i<4;i++){
            int nx = now.x + dx[i];
            int ny = now.y + dy[i];
            if(can_move(nx,ny) && now.g + 1 < f[nx][ny]){
                mps[nx][ny] = mps[nx][ny] == 'P' ? 'P' : '*' ;
                f[nx][ny] = now.g + 1;
                if(nx == now.x + 1) link[nx][ny] = 1;
                else if(nx == now.x - 1) link[nx][ny] = 3;
                else if(ny == now.y + 1) link[nx][ny] = 4;
                else if(ny == now.y - 1) link[nx][ny] = 2;
                openlist.push(Node(nx,ny,now.g + 1));
            }
        }
    }
}


int main(){
    A_bfs();
    int x = endx,y = endy;
    while(1){
        if(link[x][y] == 1) x--;
        else if(link[x][y] == 2) y++;
        else if(link[x][y] == 3) x++;
        else if(link[x][y] == 4) y--;
        if(x == beginx && y == beginy) break;
        mps[x][y] = '+';
    }

    printf("ans is : %d\n",f[endx][endy]);
    for(int i=0;i<12;i++,printf("\n")){
        for(int j=0;j<20;j++) printf("%c",mps[i][j]);
    }

    return 0;
}

/*

輸出:

ans is : 21
********************
***S++++++++++******
*************+******
*************++*****
****##########+*****
**************+*****
**************+*****
**************+*****
*************#+****.
*************#++P...
*************#**....
***************.....

符號解釋
S:起點
P:目標點
*:搜索過的路徑
+:搜索過的最優路徑
#:牆

Process returned 0 (0x0)   execution time : 0.362 s
Press any key to continue.

*/

通過比較我們可以很輕鬆的發現A*搜索相比於Dijkstra的優越性

下面是在網上找的幾組圖片來進行一些對比
圖片來源:[http://blog.csdn.net/luckyxiaoqiang/article/details/6996963]

例一

1. A*(曼哈頓距離)
這裏寫圖片描述
2. A*(歐氏距離)
這裏寫圖片描述
3. A*(切比雪夫距離)
這裏寫圖片描述
4. Dijkstra
這裏寫圖片描述
5. 雙向BFS
這裏寫圖片描述

例二

1. A*(曼哈頓距離)
這裏寫圖片描述
2. A*(歐氏距離)
這裏寫圖片描述
3. A*(切比雪夫距離)
這裏寫圖片描述
4. Dijkstra
這裏寫圖片描述
5. 雙向BFS
這裏寫圖片描述

例三

1. A*(曼哈頓距離)
這裏寫圖片描述
2. A*(歐氏距離)
這裏寫圖片描述
3. A*(切比雪夫距離)
這裏寫圖片描述
4. Dijkstra
這裏寫圖片描述
5. 雙向BFS
這裏寫圖片描述

例四

1. A*(曼哈頓距離)
這裏寫圖片描述
2. A*(歐氏距離)
這裏寫圖片描述
3. A*(切比雪夫距離)
這裏寫圖片描述
4. Dijkstra
這裏寫圖片描述
5. 雙向BFS
這裏寫圖片描述

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