如何用C++實現(xiàn)A*尋路算法
一、A*算法介紹
尋路,即找到一條從某個起點到某個終點的可通過路徑。而因為實際情況中,起點和終點之間的直線方向往往有障礙物,便需要一個搜索的算法來解決。
有一定算法基礎(chǔ)的同學(xué)可能知道從某個起點到某個終點通常使用深度優(yōu)先搜索(DFS),DFS搜索的搜索方向一般是8個方向(如果不允許搜索斜向,則有4個),但是并無優(yōu)先之分。
為了讓DFS搜索更加高效,結(jié)合貪心思想,我們給搜索方向賦予了優(yōu)先級,直觀上離終點最近的方向(直觀上的意思是無視障礙物的情況下)為最優(yōu)先搜索方向,這就是A*算法。
二、A*算法步驟解析
(如下圖,綠色為起點,紅色為終點,藍色為不可通過的墻。)

從起點開始往四周各個方向搜索。
(這里的搜索方向有8個方向)

為了區(qū)分搜索方向的優(yōu)先級,我們給每個要搜索的點賦予2個值。
G值(耗費值):指從起點走到該點要耗費的值。
H值(預(yù)測值):指從該點走到終點的預(yù)測的值(從該點到終點無視障礙物情況下預(yù)測要耗費的值,也可理解成該點到終點的直線距離的值)
在這里,值 = 要走的距離
(實際上,更復(fù)雜的游戲,因為地形不同(例如陷阱,難走的沙地之類的),還會有相應(yīng)不同的權(quán)值:值 = 要走的距離 * 地形權(quán)值)
我們還定義直著走一格的距離等于10,斜著走一格的距離等于14(因為45°斜方向的長度= sqrt(10^2+10^2) ≈ 14)
F值(優(yōu)先級值):F = G + H
這條公式意思:F是從起點經(jīng)過該點再到達終點的預(yù)測總耗費值。通過計算F值,我們可以優(yōu)先選擇F值最小的方向來進行搜索。
(每個點的左上角為F值,左下角為G值,右下角為H值)

計算出每個方向?qū)?yīng)點的F,G,H值后,
還需要給這些點賦予當(dāng)前節(jié)點的指針值(用于回溯路徑。因為一直搜下去搜到終點后,如果沒有前一個點的指針,我們將無從得知要上次經(jīng)過的是哪個點,只知道走到終點最終耗費的最小值是多少)
然后我們將這些點放入openList(開啟列表:用于存放可以搜索的點)
然后再將當(dāng)前點放入closeList(關(guān)閉列表:用于存放已經(jīng)搜索過的點,避免重復(fù)搜索同一個點)
然后再從openList取出一個F值最小(最優(yōu)先方向)的點,進行上述同樣的搜索。

在搜索過程中,如果搜索方向上的點是障礙物或者關(guān)閉列表里的點,則跳過之。
通過遞歸式的搜索,多次搜索后,最終搜到了終點。

搜到終點后,然后通過前一個點的指針值,我們便能從終點一步步回溯通過的路徑點。
(紅色標(biāo)記了便是回溯到的點)

三、A*算法優(yōu)化思路
3.1、openList使用優(yōu)先隊列(二叉堆)
可以看到openlist(開啟列表),需要實時添加點,還要每次取出最小值的點。
所以我們可以使用優(yōu)先隊列(二叉堆)來作為openList的容器。
優(yōu)先隊列(二叉堆):插入一個點的復(fù)雜度為O(logN),取出一個最值點復(fù)雜度為O(logN)
3.2、障礙物列表,closeList 使用二維表(二維數(shù)組)
由于障礙物列表和closeList僅用來檢測是否能通過,所以我們可以使用bool二維表來存放。
//假設(shè)已經(jīng)定義Width和Height分別為地圖的長和寬 bool barrierList[Width][Height]; bool closetList[Width][Height];
有某個點(Xa,Yb),可以通過
if(barrierList[Xa][Yb]&&closeList[Xa][Yb])來判斷。
因為二維表用下標(biāo)訪問,效率很高,但是耗空間比較多。(三維地圖使用三維表則更耗內(nèi)存。不過現(xiàn)在計算機一般都不缺內(nèi)存空間,所以盡量提升運算時間為主)
這是一個典型的犧牲內(nèi)存空間換取運算時間的例子。
3.3、深度限制
有時要搜的路徑非常長,利用A*算法搜一次付出的代價很高,造成游戲的卡頓。
那么為了保證每次搜索不會超過一定代價,可以設(shè)置深度限制,每搜一次則深度+1,搜到一定深度限制還沒搜到終點,則返還失敗值。
四、A*算法實現(xiàn)(C++代碼)
#include <iostream>
#include <list>
#include <vector>
#include <queue>
struct OpenPoint{
int x;
int y;
int cost; // 耗費值
int pred; // 預(yù)測值
OpenPoint* father; // 父節(jié)點
OpenPoint() = default;
OpenPoint(int pX,int pY, int endX, int endY, int c, OpenPoint* fatherp) : x(pX),y(pY),cost(c), father(fatherp) {
//相對位移x,y取絕對值
int relativeX = std::abs(endX - pX);
int relativeY = std::abs(endY - pY);
//x,y偏移值n
int n = relativeX - relativeY;
//預(yù)測值pred = (max–n)*14+n*10+c
pred = std::max(relativeX, relativeY) * 14 - std::abs(n) * 4 + c;
}
};
//比較器,用以優(yōu)先隊列的指針類型比較
struct OpenPointPtrCompare {
bool operator()(OpenPoint* a, OpenPoint* b) {
return a->pred > b->pred;
}
};
const int width = 30; //地圖長度
const int height = 100; //地圖高度
char mapBuffer[width][height]; //地圖數(shù)據(jù)
int depth = 0; //記錄深度
const int depthLimit = 2000; //深度限制
bool closeAndBarrierList[width][height]; //記錄障礙物+關(guān)閉點的二維表
//八方的位置
int direction[8][2] = { {1,0},{0,1},{-1,0},{0,-1},{1,1},{ -1,1 },{ -1,-1 },{ 1,-1 } };
//使用最大優(yōu)先隊列
std::priority_queue<OpenPoint*, std::vector<OpenPoint*>, OpenPointPtrCompare> openlist;
//存儲OpenPoint的內(nèi)存空間
std::vector<OpenPoint> pointList = std::vector<OpenPoint>(depthLimit);
//是否在障礙物或者關(guān)閉列表
inline bool inBarrierAndCloseList(int pX,int pY) {
if (pX < 0 || pY < 0 || pX >= width || pY >= height)
return true;
return closeAndBarrierList[pX][pY];
}
//創(chuàng)建一個開啟點
inline OpenPoint* createOpenPoint(int pX,int pY,int endX,int endY, int c, OpenPoint* fatherp) {
pointList.emplace_back(pX,pY,endX,endY, c, fatherp);
return &pointList.back();
}
// 開啟檢查,檢查父節(jié)點
void open(OpenPoint& pointToOpen, int endX,int endY) {
//將父節(jié)點從openlist移除
openlist.pop();
//深度+1
depth++;
//檢查p點八方的點
for (int i = 0; i < 4; ++i)
{
int toOpenX = pointToOpen.x + direction[i][0];
int toOpenY = pointToOpen.y + direction[i][1];
if (!inBarrierAndCloseList(toOpenX,toOpenY)) {
openlist.push(createOpenPoint(toOpenX, toOpenY, endX,endY, pointToOpen.cost + 10, &pointToOpen));
}
}
for (int i = 4; i < 8; ++i)
{
int toOpenX = pointToOpen.x + direction[i][0];
int toOpenY = pointToOpen.y + direction[i][1];
if (!inBarrierAndCloseList(toOpenX, toOpenY)) {
openlist.push(createOpenPoint(toOpenX, toOpenY, endX, endY, pointToOpen.cost + 14, &pointToOpen));
}
}
//最后移入closelist
closeAndBarrierList[pointToOpen.x][pointToOpen.y] = true;
}
//開始搜索路徑
std::list<OpenPoint*> findway(int startX,int startY, int endX,int endY) {
std::list<OpenPoint*> road;
// 創(chuàng)建并開啟一個父節(jié)點
openlist.push(createOpenPoint(startX,startY, endX,endY, 0, nullptr));
OpenPoint* toOpen = nullptr;
//重復(fù)尋找預(yù)測和花費之和最小節(jié)點開啟檢查
while (!openlist.empty())
{
toOpen = openlist.top();
// 找到終點后,則停止搜索
if (toOpen->x == endX && toOpen->y ==endY) {break;}//若超出一定深度(1000深度),則搜索失敗
if (depth >= depthLimit) {
toOpen = nullptr;
break;
}
open(*toOpen, endX,endY);
}
for (auto rs = toOpen; rs != nullptr; rs = rs->father) {road.push_back(rs);}
return road;
}
//創(chuàng)建地圖
void createMap() {
for (int i = 0; i < width; ++i)
for (int j = 0; j < height; ++j) {
//五分之一概率生成障礙物,不可走
if (rand() % 5 == 0) {
mapBuffer[i][j] = '*';
closeAndBarrierList[i][j] = true;
}
else {
mapBuffer[i][j] = ' ';
closeAndBarrierList[i][j] = false;
}
}
}
//打印地圖
void printMap() {
for (int i = 0; i < width; ++i) {
for (int j = 0; j < height; ++j)
std::cout << mapBuffer[i][j];
std::cout << std::endl;
}
std::cout << std::endl << std::endl << std::endl;
}
int main() {
//起點
int beginX = 0;
int beginY = 0;
//終點
int endX = 29;
int endY = 99;
//創(chuàng)建地圖
createMap();
//保證起點和終點都不是障礙物
mapBuffer[beginX][beginY] = mapBuffer[endX][endY] = ' ';
closeAndBarrierList[beginX][beginY] = closeAndBarrierList[endX][endY] = false;
//A*搜索得到一條路徑
std::list<OpenPoint*> road = findway(beginX,beginY,endX,endY);
//將A*搜索的路徑經(jīng)過的點標(biāo)記為'O'
for (auto& p : road){mapBuffer[p->x][p->y] = 'O';}
//打印走過路后的地圖
printMap();
system("pause");
return 0;
}
示例效果:

以上就是如何用C++實現(xiàn)A*尋路算法的詳細內(nèi)容,更多關(guān)于C++ A*尋路算法的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C語言實現(xiàn)BMP圖像處理(彩色圖轉(zhuǎn)灰度圖)
這篇文章主要為大家詳細介紹了C語言實現(xiàn)BMP圖像處理,彩色圖轉(zhuǎn)灰度圖,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-10-10
Visual?Studio2022的完全卸載及安裝到D盤的操作方法
這篇文章主要介紹了Visual?Studio2022的完全卸載以及完全安裝到D盤,因為VS如果隨便寫在會有很多很多的亂七八糟的東西掉出來,所以我們選擇制式一點的卸載方式,需要的朋友可以參考下2022-09-09
數(shù)據(jù)結(jié)構(gòu)之矩陣行列和相等的實例
這篇文章主要介紹了數(shù)據(jù)結(jié)構(gòu)之矩陣行列和相等的實例的相關(guān)資料,希望通過本文能幫助到大家,讓大家掌握這部分內(nèi)容,需要的朋友可以參考下2017-10-10

