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

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

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

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

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

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

三、A*算法優(yōu)化思路
3.1、openList使用優(yōu)先隊(duì)列(二叉堆)
可以看到openlist(開啟列表),需要實(shí)時(shí)添加點(diǎn),還要每次取出最小值的點(diǎn)。
所以我們可以使用優(yōu)先隊(duì)列(二叉堆)來作為openList的容器。
優(yōu)先隊(duì)列(二叉堆):插入一個(gè)點(diǎn)的復(fù)雜度為O(logN),取出一個(gè)最值點(diǎn)復(fù)雜度為O(logN)
3.2、障礙物列表,closeList 使用二維表(二維數(shù)組)
由于障礙物列表和closeList僅用來檢測是否能通過,所以我們可以使用bool二維表來存放。
//假設(shè)已經(jīng)定義Width和Height分別為地圖的長和寬 bool barrierList[Width][Height]; bool closetList[Width][Height];
有某個(gè)點(diǎn)(Xa,Yb),可以通過
if(barrierList[Xa][Yb]&&closeList[Xa][Yb])來判斷。
因?yàn)槎S表用下標(biāo)訪問,效率很高,但是耗空間比較多。(三維地圖使用三維表則更耗內(nèi)存。不過現(xiàn)在計(jì)算機(jī)一般都不缺內(nèi)存空間,所以盡量提升運(yùn)算時(shí)間為主)
這是一個(gè)典型的犧牲內(nèi)存空間換取運(yùn)算時(shí)間的例子。
3.3、深度限制
有時(shí)要搜的路徑非常長,利用A*算法搜一次付出的代價(jià)很高,造成游戲的卡頓。
那么為了保證每次搜索不會(huì)超過一定代價(jià),可以設(shè)置深度限制,每搜一次則深度+1,搜到一定深度限制還沒搜到終點(diǎn),則返還失敗值。
四、A*算法實(shí)現(xiàn)(C++代碼)
#include <iostream>
#include <list>
#include <vector>
#include <queue>
struct OpenPoint{
int x;
int y;
int cost; // 耗費(fèi)值
int pred; // 預(yù)測值
OpenPoint* father; // 父節(jié)點(diǎn)
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)先隊(duì)列的指針類型比較
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)閉點(diǎn)的二維表
//八方的位置
int direction[8][2] = { {1,0},{0,1},{-1,0},{0,-1},{1,1},{ -1,1 },{ -1,-1 },{ 1,-1 } };
//使用最大優(yōu)先隊(duì)列
std::priority_queue<OpenPoint*, std::vector<OpenPoint*>, OpenPointPtrCompare> openlist;
//存儲(chǔ)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)建一個(gè)開啟點(diǎn)
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é)點(diǎn)
void open(OpenPoint& pointToOpen, int endX,int endY) {
//將父節(jié)點(diǎn)從openlist移除
openlist.pop();
//深度+1
depth++;
//檢查p點(diǎn)八方的點(diǎn)
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)建并開啟一個(gè)父節(jié)點(diǎn)
openlist.push(createOpenPoint(startX,startY, endX,endY, 0, nullptr));
OpenPoint* toOpen = nullptr;
//重復(fù)尋找預(yù)測和花費(fèi)之和最小節(jié)點(diǎn)開啟檢查
while (!openlist.empty())
{
toOpen = openlist.top();
// 找到終點(diǎn)后,則停止搜索
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() {
//起點(diǎn)
int beginX = 0;
int beginY = 0;
//終點(diǎn)
int endX = 29;
int endY = 99;
//創(chuàng)建地圖
createMap();
//保證起點(diǎn)和終點(diǎn)都不是障礙物
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)過的點(diǎn)標(biāo)記為'O'
for (auto& p : road){mapBuffer[p->x][p->y] = 'O';}
//打印走過路后的地圖
printMap();
system("pause");
return 0;
}
示例效果:

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

