天天看點

高效尋路算法——A*(A-Star)

在資料結構中我們學過圖,而圖中一個很重要的課題就是與最短路徑、最優路徑相關的尋路問題,包括Dijkstra、深度優先搜尋,都是其中的經典算法;

同時,在遊戲開發中,也常常需要設計合适的尋路算法來實作怪物AI的移動、人物自動尋路等常用功能,在各種算法中,A*無疑是最常用也是最經典的一種,作為進一步了解遊戲尋路機制的基礎,A*的學習很有必要,是以接下來這篇文章會從原理入手,一步步解析算法的過程并且實作它

高效尋路算法——A*(A-Star)

圖0

經典RPG中人物NPC的移動往往就需要合适的尋路算法,地圖的網格化就是為了實作這點

一、算法介紹

       A*算法是靜态網絡中求解最短路徑時最有效的直接搜尋算法,也是許多算法的啟發式算法。因其簡單高效的特點,常常會用作遊戲中的尋路算法。當然要注意,在直接搜尋算法中它是最有效的,後來也出現了許多預處理算法(如ALT,CH,HL等等),線上查詢效率是A*算法的數千甚至上萬倍。

二、算法原理

        A*算法的本質似乎是一種貪心選擇政策,即走的每一步都是目前狀況下的最優選擇,而A*算法的貪心選擇依據主要是三個值F、H和G和兩個表openList和closeList;首先先來解釋一下它們各自的含義:

        G——從起點到達目前位置點的移動代價/距離,這個值是會不斷累加并更新的;我們将橫向或縱向(即上下左右)移動一格的代價定為10,那麼斜向移動一格的代價可以定為14(即

高效尋路算法——A*(A-Star)

),使用10作為機關代價是為了避免小數計算。不同的路徑會使得起點到達目前位置的移動代價并不相同,但是我們總是傾向于更小的G值,這是由貪心選擇政策導緻的,後面會看到這一點。

        H——從目前位置到達終點的估計代價/距離,計算估計代價的方法有很多,我們這裡僅讨論一種較為簡單且常用的方法即曼哈頓(Manhattan)距離,曼哈頓距離表示兩個點的絕對軸距總和,如圖1所示。

高效尋路算法——A*(A-Star)

圖1

       F——目前位置的總代價,也是進行貪心選擇的最終依據,其值為移動代價與估計代價的值的和,即F = G + H。

       openList——開放清單,用于儲存目前可以到達的所有位置節點;我們每次都要從openList中尋找F值最小的節點作為目前節點,然後再将目前節點周圍所有可到達的點加入到openList當中并将目前節點設定為它們的父節點,如果已經在openList當中,則需要判斷從這個點到目前節點的代價是否更小,有必要則進行更新。這樣,當把終點加入到openList中時,就可以回溯父節點找到一條最短路徑;如果到openList為空時,終點仍未添加進去,則說明查找失敗,無法到達終點。

       closeList——關閉清單,用于儲存開放清單中處理完的節點,保證處理過的節點不再處理,通常可以和圖中不可到達的位置記錄在同一個表中。

三、算法過程

1、将起點加入到openList中(每次将點加入到openList當中時,都要計算并儲存它們的F值,後面不再贅述),将所有不可到達的點記錄在closeList當中

2、重複進行下面步驟,直到openList為空或者将終點添加至openList中:

(1)從openList中取出F值最小的節點,将該點作為目前要處理的節點;

(2)周遊該目前點周圍的可到達點(即八個方向且不在closeList中的點),如果這個可到達點不在openList中,直接将其加入進來,并将它的父節點設定為目前點;如果這個可到達點已經在openList當中,那麼需要比較經由這個點到達目前處理點的路徑代價是否會更小(因為H值不變,是以主要目标是比較G值大小),如果代價更小,那麼就改變目前處理點的父節點為這個點,否則不作任何處理,這樣是為了保證到達目前處理點的路徑是最短的;

高效尋路算法——A*(A-Star)

圖2

例如圖2所示,考察目前處理點周圍的可加入點時,因為其上方的格子已經在openList當中,是以需要比較經由其上方格子到達目前格子的代價是否更小,很明顯

Ga = 10 + 10 = 20;

Gb = 14;

Gb < Ga;

是以比較而言路線b的代價更小,目前待處理格子不需要作任何改變

(3)将該處理點記錄到closeList當中

3、從終點開始,開始沿着父節點周遊至起點,就是利用A*算法所得到的最短路徑

如圖3456,以一個執行個體的形式展現了尋找從起點A到終點B的最短路徑的過程

高效尋路算法——A*(A-Star)

圖3

一開始openList當中隻有A一個節點,是以取出F值最小的節點也就是A作為目前的待處理節點

高效尋路算法——A*(A-Star)

圖4

處理節點A,将其周圍所有的可到達節點加入到openList中,因為這些可到達節點一開始都不在openList當中,是以不需要比較G值直接加入并将A設定為他們的父節點,然後取出openList中F值最小等于34的節點即右下角的節點作為下一個待處理節點;這樣節點A處理完畢,将A加入到closeList當中

高效尋路算法——A*(A-Star)

圖5

處理目前F值等于34的節點,先考察其周圍的節點,節點A因為已經處理完畢置于closeList當中,其右上角和右邊的節點是不可到達節點,是以這些點無需考慮;其下方三個節點并不在openList當中,是以直接将他們加入openList,最後考察剩下兩個點,比較可得經過他們的G值均大于原來的G值,是以它們并非是更優路徑,不作任何改變;如此一來目前F值等于34的這個節點也處理完畢,我們将其加入到closeList當中

高效尋路算法——A*(A-Star)

圖6

反複重複上面的步驟,最終将B節點加入到openList當中,表示搜尋成功,然後我們從終點B開始,通路它們的父節點,就可以得到一條從A到B的最短路徑啦!即圖中棕色箭頭表示的路徑

四、代碼實作

#define MAX 10
#include<iostream>
#include<math.h>
#include<string.h>
#include<stack>
#include"priorQue.h"

using namespace std;

typedef struct vector2 {
	int x, y;
}vector2;
//定義一個二維向量結構體

typedef struct AStarNode {
	vector2 pos, parent;
	int F, G, H;  //節點的位置和代價

	AStarNode() {}
	AStarNode(int x1, int y1, int x2, int y2) {
		parent = { -1, -1 };  pos = { x1, y1 };
		H = abs(x1 - y1) + abs(x2 - y2);
		G = 0;  F = H;
	}
	//構造函數,用于初始化位置坐标和計算估計代價

	bool operator<(const AStarNode &n)const {
		return this->F < n.F;
	}
	//重載運算符小于号,這樣才能用最小堆進行存儲
}AStarNode;
//A*節點

bool AStar(int visit[][MAX], int x1, int y1, int x2, int y2) //傳入的參數分别是地圖資訊、起點的xy坐标、終點的xy坐标
{
	/*
	visit數組用于記錄圖中點的目前狀态
	如果等于0,代表這個點不可到達或者已經被置入closeList當中,是以visit數組實作了closeList表的功能
	如果等于1,為正常狀态,說明這個點能走通
	如果等于2,說明見目前這個點位于openList當中
	*/

	AStarNode a[MAX][MAX];
	//開一個與圖等大的節點數組
	vector2 around[8] = { {-1, 1}, {0, 1}, {1, 1}, {-1, 0}, {1, 0}, {-1, -1}, {0, -1}, {1, -1} }, tmp = { 0, 0 };
	//around數組表示一個點周圍的八個方向
	AStarNode start(x1, y1, x2, y2), current;
	a[x1][y1] = start;
	//初始化起點和目前待處理的節點
	bool flag = false;
	//記錄終點是否被加入到openList當中
	stack<AStarNode> record;

	priorQue<AStarNode> openList;
	openList.enQueue(start);
	//将openList用優先級隊列來表示,這樣隊列首部第一個節點自然就是F值最小的節點
	//先将起點加入到openList中去

	while (!openList.empty() && !flag) 
	{
		current = openList.deQueue();  //從openList中選出F值最小的作為目前待處理的節點
		for (int i = 0; i < 8; i++) 
		{
			tmp.x = current.pos.x + around[i].x;
			tmp.y = current.pos.y + around[i].y;
			if (tmp.x < 0 || tmp.x > MAX - 1 || tmp.y < 0 || tmp.y > MAX - 1 || visit[tmp.x][tmp.y] == 0) continue;
			//得到目前待處理點周圍的一個點,若這個點超出邊界或者為不可到達點,則跳過不作處理
                        if (around[i].x != 0 && around[i].y != 0 && 
                            visit[current.pos.x + around[i].x][current.pos.y] == 0 && visit[current.pos.x][current.pos.y + around[i].y] == 0) continue;
                        //另外一種情況,即側向雖然可達,但無法直接到達,也應跳過

			if (visit[tmp.x][tmp.y] == 1) {
				//如果visit等于1
				AStarNode n(tmp.x, tmp.y, x2, y2);
				n.parent = current.pos;
				if (abs(around[i].x) + abs(around[i].y) > 1) n.G = current.G + 14;
				else n.G = current.G + 10;
				n.F = n.G + n.H;
				//計算這個點的G值和F值
				openList.enQueue(n);
				a[tmp.x][tmp.y] = n;
				visit[tmp.x][tmp.y] = 2;
				//直接加入openList當中
				if (tmp.x == x2 && tmp.y == y2) {
					flag = true;
					current = n;
				}
				//如果這個加入點為終點,将标記記為true
			}
			else {
				//如果visit等于2
				AStarNode n = a[tmp.x][tmp.y];
				int price = 10;
				if (abs(around[i].x) + abs(around[i].y) > 1) price = 14;
				if (n.G + price < current.G) {
					current.G = n.G + price;
					current.parent = n.pos;
				}
				//需要判斷從這個點到達待處理點是否為更優路徑,如果是則進行改動
			}
		}
	}
	if (flag) {
		while (current.pos.x != x1 || current.pos.y != y1) {
			record.push(current);
			current = a[current.parent.x][current.parent.y];
		}
		record.push(current);
		while (!record.empty()) {
			cout << "(" << record.top().pos.x << " ," << record.top().pos.y << ") " << record.top().F << endl;
			record.pop();
		}
	}
	//終點加入openList中,說明搜尋成功,從終點開始通路父節點至起點并用棧來儲存;之後再周遊一遍棧将路徑列印出來

	return flag;
}
//A*算法主體

int main()
{
	int map[MAX][MAX] = {
		1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
		1, 1, 1, 1, 1, 0, 1, 1, 0, 1,
		1, 1, 0, 1, 1, 1, 0, 1, 1, 1,
		1, 1, 1, 1, 0, 1, 1, 1, 1, 1,
		1, 1, 1, 1, 1, 0, 0, 0, 1, 1,
		1, 0, 1, 1, 0, 0, 1, 1, 0, 1,
		1, 1, 1, 1, 0, 1, 0, 1, 1, 1,
		1, 0, 1, 0, 1, 1, 1, 0, 1, 1,
		1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
		1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
	};
	cout << AStar(map, 4, 4, 9, 9) << endl;

	system("pause");
}
//編造資料進行測試
           

五、結果示範

将最短路徑的節點坐标和F值依次列印出來(測試資料較少,有錯誤歡迎随時指正)。

結果如圖7所示:

高效尋路算法——A*(A-Star)

圖7

高效尋路算法——A*(A-Star)

圖8 根據程式運作結果畫出的最優路徑

參考文章

https://blog.csdn.net/hitwhylz/article/details/23089415

繼續閱讀