A*啓發式搜索基礎
傳統的搜索方式是盲目搜索,即到每一步的時候並沒有對每種情況進行有效的區分,這樣的結果是浪費了大量的時間,對很多沒有必要的數據進行了搜索。
而A*算法則在搜索的過程中會選取認爲“最優”的狀態進行搜索,而這正是這種算法的精華部分。
其實我們可以將他和Dijkstra進行一定的對比,他們的共同點很多,都是每次選取最優的點開始搜索,但是他們的“最優”策略不同也就決定了不同的效率。
- Dijkstra是每次選取距離“出發點”最近的點開始搜索
- A*算法則是選取距離“目標點”估計值最小的點開始搜索
注意這裏的估計值就是A*算法的核心部分了,本博文最後會介紹和Dijkstra的具體效率的區別。
A*算法將點到重點的距離分爲兩個部分:
F(n)=G(n)+H(n)
介紹:
F(n) 表示起點到目標的估計距離G(n) 表示起點到n點的準確距離H(n) 表示n點到目標點的估計距離
我們先假設從
這裏
H(n)<=H′(n)
需要說明的是,我們A*搜索的核心就是
爲了和大多數的講解名字對應,本博文采用相同名字,即名openlist的優先隊列來存放需要搜索的內容,具體搜索步驟如下
- 預定義
H(n)=|beginx−endx|+|beginy−endy| - 預定義
F(n)=G(n)+H(n) openlist中
F(n) 值小的優先級別高
- 將起點加入openlist中
- 從隊列中彈出元素
- 對彈出元素的周邊進行搜索,如果發現有未被搜索到的位置,並且不是牆,河水,峽谷等不能行走的位置,則將其加入到隊列中,並將其指向彈出元素。
如果發現有已經被搜索過的位置,則查看其F(n) 值是否比現在走到那個位置的F(n) 值小,若現在是更優解,則更新其F(n) 值。如果其不在隊列中則把其重新加入隊列,否則什麼也不用做。- 重複2-4步,直到找到目標點
- 最小路徑即目標點的
F(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