如何用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ù)測(cè)值):指從該點(diǎn)走到終點(diǎn)的預(yù)測(cè)的值(從該點(diǎn)到終點(diǎn)無視障礙物情況下預(yù)測(cè)要耗費(fèi)的值,也可理解成該點(diǎn)到終點(diǎn)的直線距離的值)
在這里,值 = 要走的距離
(實(shí)際上,更復(fù)雜的游戲,因?yàn)榈匦尾煌ɡ缦葳?,難走的沙地之類的),還會(huì)有相應(yīng)不同的權(quán)值:值 = 要走的距離 * 地形權(quán)值)
我們還定義直著走一格的距離等于10,斜著走一格的距離等于14(因?yàn)?5°斜方向的長(zhǎng)度= sqrt(10^2+10^2) ≈ 14)
F值(優(yōu)先級(jí)值):F = G + H
這條公式意思:F是從起點(diǎn)經(jīng)過該點(diǎn)再到達(dá)終點(diǎn)的預(yù)測(cè)總耗費(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僅用來檢測(cè)是否能通過,所以我們可以使用bool二維表來存放。
//假設(shè)已經(jīng)定義Width和Height分別為地圖的長(zhǎng)和寬 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í)要搜的路徑非常長(zhǎng),利用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ù)測(cè)值 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) { //相對(duì)位移x,y取絕對(duì)值 int relativeX = std::abs(endX - pX); int relativeY = std::abs(endY - pY); //x,y偏移值n int n = relativeX - relativeY; //預(yù)測(cè)值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; //地圖長(zhǎng)度 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ù)測(cè)和花費(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*尋路算法的資料請(qǐng)關(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-02C++使用智能指針實(shí)現(xiàn)模板形式的單例類
這篇文章主要為大家詳細(xì)介紹了C++使用了智能指針實(shí)現(xiàn)模板形式的單例類,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06C語言實(shí)現(xiàn)BMP圖像處理(彩色圖轉(zhuǎn)灰度圖)
這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)BMP圖像處理,彩色圖轉(zhuǎn)灰度圖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10Visual?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