1.算法描述
A*搜尋算法俗稱A星算法,是比較流行的啓發式搜索算法之一,被廣泛應用於路徑優化領域。它的獨特之處是檢查最短路徑中每個可能的節點時引入了全局信息,對當前節點距終點的距離做出估計,並作爲評價該節點處於最短路線上的可能性的量度。
A*改變它自己行爲的能力基於啓發式代價函數,啓發式函數在遊戲中非常有用。在速度和精確度之間取得折衷將會讓你的遊戲運行得更快。在很多遊戲中,你並不真正需要得到最好的路徑,僅需要近似的就足夠了。而你需要什麼則取決於遊戲中發生着什麼,或者運行遊戲的機器有多快。
在本實驗中,爲了解決羅馬尼亞度假問題而引入的A算法,目的是爲了尋找一個城市到另一個城市的最短路徑,A算法
擴展:本來想些啓發式算法的,但是突然迷茫了一下,可以以後再寫,好好的學一學。當我寫這些東西的時候,我發現,自己還不是很懂。
以上多數文字直接來源於度娘百科,沒有過多的修改。我對於A*算法的描述,着實少之又少,感覺自己又回到了最初的懵懂狀態。
我需要一個比較好的數據集來解釋自己想要時候的,直接用數據來描述自己想要表達的。
2.問題描述
旅行商問題:
In the following map with 20 cities, please find a route from Zerind to Bucharest using A search.*
[在一個擁有20個城市的圖中,使用A星算法,找到一條最優路徑從塞爾維亞到羅馬尼亞]
解釋:上述圖中,線上的數字表示兩個城市的實際距離。
右側的數字表示到Bucharest[終點]的估計距離。
每個城市的首字母都不一樣,可以數字化。
3.算法原理
A*算法是一種搜索算法,在算法過程中引入了F,G,H三個量。
下面引入具體數據,來演示A*算法:
.
說明:
注意事項:
過程:
.
.
.
.
, ,
.
, ,,
.
, ,,,
.
, ,,,
.
, ,,,,
.
, ,,,,,
.
, ,,,,,,,
.回溯:,,,,,,,
最終結果:
具體的實驗過程中需要用某種數據結構來保存父節點,以便回溯,而且最終G結點的F值,就是實際路程。
自我感覺良好,上面的那一段我輸入了好長時間。不知道是什麼原因,過程特別的卡頓,我猜是因爲要解析好多的符號,可能需要消耗時間,比較少的時候還好,很多的話,就不如插入一張圖片。這也是瀏覽器加載的時候需要注意的,加載一張圖遠比加載好多圖來得快,所以會將好多圖標都繪製在一張圖上,在網頁中只顯示圖片中的某一部分,這樣一來,打開這樣的網頁,圖片就只加載了一次。
流程圖如下:
這是很久以前繪製的流程圖,可能不是很準確但是是根據僞代碼畫出來的,應該不會有錯。
僞代碼:
4.算法源碼
#!/usr/bin/python
# -*- coding: UTF-8 -*-
# redefine place
A = 0 # Arad
B = 1 # Bucharest
C = 2 # Craiova
D = 3 # Dobreta
E = 4 # Eforie
F = 5 # Fagaras
G = 6 # Giurgiu
H = 7 # Hirsova
I = 8 # Iasi
L = 9 # Lugoj
M = 10 # Mehadia
N = 11 # Neamt
O = 12 # Oradea
P = 13 # Pitesti
R = 14 # Rimnicu Vilcea
S = 15 # Sibiu
T = 16 # Timisoara
U = 17 # Urziceni
V = 18 # Vaslui
Z = 19 # Zerind
# 構建一個20*20的矩陣
PLACES_NUM = 20
# 地圖
class Graph:
def __init__(self):
self.romania_graph = [[0] * PLACES_NUM for i in range(PLACES_NUM)]
def add_edge(self, _from, _to, _value):
if _from < PLACES_NUM:
if _to < PLACES_NUM:
self.romania_graph[_from][_to] = _value
self.romania_graph[_to][_from] = _value
def get_edge(self, _from, _to):
return self.romania_graph[_from][_to]
#問題的最終解
class Stack:
def __init__(self):
self.stack = []
def push(self, value):
self.stack.append(value)
return
def pop(self):
return self.stack.pop()
def is_empty(self):
if self.stack:
return False
return True
# CLOSED表
class Queue:
def __init__(self):
self.queue = []
def put(self, value):
self.queue.append(value)
return
def get(self):
return self.queue.pop(0)
def contain(self, value):
return value in self.queue
def is_empty(self):
if self.queue:
return False
return True
# OPEN表
class PriorityQueue:
def __init__(self):
self.queue = []
def put(self, node_cost):
"""
:param node_cost: [value,cost]
"""
self.queue.append(node_cost)
def get(self):
if self.queue:
min_i = 0
min_cost = self.queue[min_i][1]
for i in range(len(self.queue)):
if self.queue[i][1] < min_cost:
min_i = i
min_cost = self.queue[i][1]
return self.queue.pop(min_i)
def contain(self,value):
for i in range(len(self.queue)):
if self.queue[i][0]==value:
return self.queue[i],True
return None,False
def is_empty(self):
if self.queue:
return False
return True
# initialize undirected graph
# 初始化無向圖
def init_graph():
graph = Graph()
graph.add_edge(O, Z, 71)
graph.add_edge(O, S, 151)
graph.add_edge(A, Z, 75)
graph.add_edge(A, S, 140)
graph.add_edge(A, T, 118)
graph.add_edge(T, L, 111)
graph.add_edge(L, M, 70)
graph.add_edge(M, D, 75)
graph.add_edge(D, C, 120)
graph.add_edge(S, R, 80)
graph.add_edge(S, F, 99)
graph.add_edge(R, C, 146)
graph.add_edge(F, B, 211)
graph.add_edge(R, P, 97)
graph.add_edge(C, P, 138)
graph.add_edge(P, B, 101)
graph.add_edge(B, G, 90)
graph.add_edge(B, U, 85)
graph.add_edge(U, H, 98)
graph.add_edge(U, V, 142)
graph.add_edge(V, I, 92)
graph.add_edge(I, N, 87)
graph.add_edge(H, E, 86)
return graph
def a_star(graph, h, _root, _goal):
g = [0] * PLACES_NUM # g(n) value 現階段搜索過程中的每個結點的g(n)記錄@
parents = [0] * PLACES_NUM # temporary parents 現階段搜索過程中的每個節點的父結點記錄@
pq_open = PriorityQueue() # open queue 初始化open表@
q_close = Queue() # closed queue 初始化closed表@
s_path = Stack() # solution nodes path s_path中作爲走過路徑記錄@
s_parent = Stack() # solution nodes' parents path s_parent中作爲open表每次pop的記錄@
pq_open.put([_root, 0]) #將初始結點存入OPEN表中@
while pq_open.is_empty()==False:
parrent_node=pq_open.get()
if parrent_node[0]==_goal:
break
q_close.put(parrent_node[0])
for i in range(20):
length=graph.get_edge(parrent_node[0],i)
if length!=0:
node,result=pq_open.contain(i)
f=parrent_node[1]-h[parrent_node[0]]+length+h[i]
if q_close.contain(i):
continue
elif result==True:
if node[1]>f:
node.pop()
parents[i]=parrent_node[0]
pq_open.put([i,f])
else:
parents[i]=parrent_node[0]
pq_open.put([i,f])
path=[]
cost=0
print("parents:",parents)
p=_goal
while p!=_root:
cost+=graph.get_edge(p,parents[p])
path.append(p);
p=parents[p]
length=len(path)-1
print('path:',_root,end='')
for i in range(length+1):
print(" -->",path[length-i],end='')
print()
return cost
if __name__ == '__main__':
graph = init_graph()
# 怎麼才能夠獲取到這個矩陣
h = (366, 0, 160, 242, 161,
178, 77, 151, 226, 244,
241, 234, 380, 98, 193,
253, 329, 80, 199, 374)
# place_str = input()
# places = place_str.split()
# cost = a_star(graph, h, eval(places[0]), eval(places[1]))
print("node","A"," B")
cost = a_star(graph, h,eval('A'),eval('B'))
print('cost:',cost)
print()
print("node","C"," B")
cost = a_star(graph, h,eval('C'),eval('B'))
print('cost:',cost)
print()
print("node","U"," B")
cost = a_star(graph, h,eval('U'),eval('B'))
print('cost:',cost)
print()
備註:
- 保存好源碼之後,自己多寫幾個print來看一看各個數據都是什麼,避免超級低級的錯誤。
- 源碼總註釋比較少,爲何?因爲有了流程圖,以及上面的展示,A*算法的思想還是很容易就可以理解的,而且算法部分的代碼很容易就能夠看懂。
話說我也不知道是爲什麼,我上傳代碼之後竟然沒有高亮顯示,感覺很奇怪,是不是我等級尚低,修爲尚淺,還是因爲之前的代碼都是MATLAB代碼。- 代碼最後的部分,註釋掉的三行是用來控制檯輸入的,但是鑑於Sublime Text3中input()函數不好用,我直接弄成了固定的測試數據。
- 代碼中最後的cost計算,可以直接從A*算法中得到,
並不需要使用回溯的方法,所以我這裏這樣寫,純屬畫蛇添足,但是因爲要輸出路徑,所以順帶算了一下。
5.算法輸出
6.總結
不難。但是卻囿於有限的知識,而不能夠將其進行擴展,A*算法是一種比較好的尋路算法,在遊戲中會經常使用到,在Unity中有特定的尋路算法,也有一部分人是自己寫的算法,自己寫的話有什麼好處呢?
倘若是自己寫的尋路算法,在尋路的時候,可以給AI加上好多東西,這樣可以讓AI看上去不是那麼傻?現在還是有一些疑惑的,尋路的時候是將地圖當成了一個類似於座標系一樣的東西,中間遇到障礙物就會換路,而且是找到一條路徑之後,AI纔會出發,也就是說AI提前就知道一條路徑,o(  ̄︶ ̄ )o。
那麼怎麼把現在的這種數據跟遊戲中尋路數據對應起來呢?好暈。找了一些個鏈接,有時間的時候可以看一看,或者就最近這一段時間看一看。
鏈接如下:
- A星算法詳解(個人認爲最詳細,最通俗易懂的一個版本)
- A* Pathfinding for Beginners(不用外網)
- 堪稱最好最全的A*算法詳解(譯文)
- Amit’s A* Pages(不用外網)
應該嘗試用各種編程語言寫該算法,因爲不能容忍自己的菜!!!