用python实现基本A*算法
本文由戀花蝶發(fā)表于http://blog.csdn.net/lanphaday,可以在保證全文完整的前提下任意形式自由傳播,但必須保留本聲明,違反必究。
?最近因?yàn)橐粋€(gè)任務(wù)要用到A*算法,就用C++實(shí)現(xiàn)了一份。不過只是用A*來檢測從A點(diǎn)到B點(diǎn)有無通路,不必輸出路徑,后來想把代碼貼出來,但又覺得不如實(shí)現(xiàn)一個(gè)簡單的尋路應(yīng)用好一些,就用python寫了一個(gè)版本貼上來。
?A*算法不僅僅可以用來尋路,尋路也不僅僅使用A*算法。這是使用學(xué)習(xí)和使用A*算法最要謹(jǐn)記的一點(diǎn)吧~
?A*算法用以尋路實(shí)現(xiàn)算不得是人工智能,他本質(zhì)上是一種啟發(fā)式的試探回溯算法,不過業(yè)界似乎喜歡把它稱為游戲人工智能(GameAI)的一個(gè)組成部分,聽起來就“豪華”得多了。A*算法需要很大的內(nèi)存(相對(duì)于深度優(yōu)先搜索),需要很實(shí)現(xiàn)比較復(fù)雜的邏輯,容易出錯(cuò)。
?A*過程:
?1.將開始節(jié)點(diǎn)放入開放列表(開始節(jié)點(diǎn)的F和G值都視為0);
?2.重復(fù)一下步驟:
??i.在開放列表中查找具有最小F值的節(jié)點(diǎn),并把查找到的節(jié)點(diǎn)作為當(dāng)前節(jié)點(diǎn);
??ii.把當(dāng)前節(jié)點(diǎn)從開放列表刪除, 加入到封閉列表;
??iii.對(duì)當(dāng)前節(jié)點(diǎn)相鄰的每一個(gè)節(jié)點(diǎn)依次執(zhí)行以下步驟:
???1.如果該相鄰節(jié)點(diǎn)不可通行或者該相鄰節(jié)點(diǎn)已經(jīng)在封閉列表中,則什么操作也不執(zhí)行,繼續(xù)檢驗(yàn)下一個(gè)節(jié)點(diǎn);
???2.如果該相鄰節(jié)點(diǎn)不在開放列表中,則將該節(jié)點(diǎn)添加到開放列表中, 并將該相鄰節(jié)點(diǎn)的父節(jié)點(diǎn)設(shè)為當(dāng)前節(jié)點(diǎn),同時(shí)保存該相鄰節(jié)點(diǎn)的G和F值;
???3.如果該相鄰節(jié)點(diǎn)在開放列表中, 則判斷若經(jīng)由當(dāng)前節(jié)點(diǎn)到達(dá)該相鄰節(jié)點(diǎn)的G值是否小于原來保存的G值,若小于,則將該相鄰節(jié)點(diǎn)的父節(jié)點(diǎn)設(shè)為當(dāng)前節(jié)點(diǎn),并重新設(shè)置該相鄰節(jié)點(diǎn)的G和F值.
??iv.循環(huán)結(jié)束條件:
???當(dāng)終點(diǎn)節(jié)點(diǎn)被加入到開放列表作為待檢驗(yàn)節(jié)點(diǎn)時(shí), 表示路徑被找到,此時(shí)應(yīng)終止循環(huán);
???或者當(dāng)開放列表為空,表明已無可以添加的新節(jié)點(diǎn),而已檢驗(yàn)的節(jié)點(diǎn)中沒有終點(diǎn)節(jié)點(diǎn)則意味著路徑無法被找到,此時(shí)也結(jié)束循環(huán);
?3.從終點(diǎn)節(jié)點(diǎn)開始沿父節(jié)點(diǎn)遍歷, 并保存整個(gè)遍歷到的節(jié)點(diǎn)坐標(biāo),遍歷所得的節(jié)點(diǎn)就是最后得到的路徑;
?好了,廢話不多說,看代碼吧,帶詳盡注釋,但可能存在bug~,另:本示例程序未作優(yōu)化。
參考資料:
http://www.gamedev.net/reference/programming/features/astar/default.asp
http://blog.csdn.net/win32asn/archive/2006/03/17/627098.aspx
# -*- coding: utf-8 -*- import math#地圖 tm = [ '############################################################', '#..........................................................#', '#.............................#............................#', '#.............................#............................#', '#.............................#............................#', '#.......S.....................#............................#', '#.............................#............................#', '#.............................#............................#', '#.............................#............................#', '#.............................#............................#', '#.............................#............................#', '#.............................#............................#', '#.............................#............................#', '#######.#######################################............#', '#....#........#............................................#', '#....#........#............................................#', '#....##########............................................#', '#..........................................................#', '#..........................................................#', '#..........................................................#', '#..........................................................#', '#..........................................................#', '#...............................##############.............#', '#...............................#........E...#.............#', '#...............................#............#.............#', '#...............................#............#.............#', '#...............................#............#.............#', '#...............................###########..#.............#', '#..........................................................#', '#..........................................................#', '############################################################']#因?yàn)閜ython里string不能直接改變某一元素,所以用test_map來存儲(chǔ)搜索時(shí)的地圖 test_map = []######################################################### class Node_Elem:"""開放列表和關(guān)閉列表的元素類型,parent用來在成功的時(shí)候回溯路徑"""def __init__(self, parent, x, y, dist):self.parent = parentself.x = xself.y = yself.dist = distclass A_Star:"""A星算法實(shí)現(xiàn)類"""#注意w,h兩個(gè)參數(shù),如果你修改了地圖,需要傳入一個(gè)正確值或者修改這里的默認(rèn)參數(shù)def __init__(self, s_x, s_y, e_x, e_y, w=60, h=30):self.s_x = s_xself.s_y = s_yself.e_x = e_xself.e_y = e_yself.width = wself.height = hself.open = []self.close = []self.path = []#查找路徑的入口函數(shù)def find_path(self):#構(gòu)建開始節(jié)點(diǎn)p = Node_Elem(None, self.s_x, self.s_y, 0.0)while True:#擴(kuò)展F值最小的節(jié)點(diǎn)self.extend_round(p)#如果開放列表為空,則不存在路徑,返回if not self.open:return#獲取F值最小的節(jié)點(diǎn)idx, p = self.get_best()#找到路徑,生成路徑,返回if self.is_target(p):self.make_path(p)return#把此節(jié)點(diǎn)壓入關(guān)閉列表,并從開放列表里刪除self.close.append(p)del self.open[idx]def make_path(self,p):#從結(jié)束點(diǎn)回溯到開始點(diǎn),開始點(diǎn)的parent == Nonewhile p:self.path.append((p.x, p.y))p = p.parentdef is_target(self, i):return i.x == self.e_x and i.y == self.e_ydef get_best(self):best = Nonebv = 1000000 #如果你修改的地圖很大,可能需要修改這個(gè)值bi = -1for idx, i in enumerate(self.open):value = self.get_dist(i)#獲取F值if value < bv:#比以前的更好,即F值更小best = ibv = valuebi = idxreturn bi, bestdef get_dist(self, i):# F = G + H# G 為已經(jīng)走過的路徑長度, H為估計(jì)還要走多遠(yuǎn)# 這個(gè)公式就是A*算法的精華了。return i.dist + math.sqrt((self.e_x-i.x)*(self.e_x-i.x)+ (self.e_y-i.y)*(self.e_y-i.y))*1.2def extend_round(self, p):#可以從8個(gè)方向走xs = (-1, 0, 1, -1, 1, -1, 0, 1)ys = (-1,-1,-1, 0, 0, 1, 1, 1)#只能走上下左右四個(gè)方向 # xs = (0, -1, 1, 0) # ys = (-1, 0, 0, 1)for x, y in zip(xs, ys):new_x, new_y = x + p.x, y + p.y#無效或者不可行走區(qū)域,則勿略if not self.is_valid_coord(new_x, new_y):continue#構(gòu)造新的節(jié)點(diǎn)node = Node_Elem(p, new_x, new_y, p.dist+self.get_cost(p.x, p.y, new_x, new_y))#新節(jié)點(diǎn)在關(guān)閉列表,則忽略if self.node_in_close(node):continuei = self.node_in_open(node)if i != -1:#新節(jié)點(diǎn)在開放列表if self.open[i].dist > node.dist:#現(xiàn)在的路徑到比以前到這個(gè)節(jié)點(diǎn)的路徑更好~#則使用現(xiàn)在的路徑self.open[i].parent = pself.open[i].dist = node.distcontinueself.open.append(node)def get_cost(self, x1, y1, x2, y2):"""上下左右直走,代價(jià)為1.0,斜走,代價(jià)為1.4"""if x1 == x2 or y1 == y2:return 1.0return 1.4def node_in_close(self, node):for i in self.close:if node.x == i.x and node.y == i.y:return Truereturn Falsedef node_in_open(self, node):for i, n in enumerate(self.open):if node.x == n.x and node.y == n.y:return ireturn -1def is_valid_coord(self, x, y):if x < 0 or x >= self.width or y < 0 or y >= self.height:return Falsereturn test_map[y][x] != '#'def get_searched(self):l = []for i in self.open:l.append((i.x, i.y))for i in self.close:l.append((i.x, i.y))return l######################################################### def print_test_map():"""打印搜索后的地圖"""for line in test_map:print ''.join(line)def get_start_XY():return get_symbol_XY('S')def get_end_XY():return get_symbol_XY('E')def get_symbol_XY(s):for y, line in enumerate(test_map):try:x = line.index(s)except:continueelse:breakreturn x, y######################################################### def mark_path(l):mark_symbol(l, '*')def mark_searched(l):mark_symbol(l, ' ')def mark_symbol(l, s):for x, y in l:test_map[y][x] = sdef mark_start_end(s_x, s_y, e_x, e_y):test_map[s_y][s_x] = 'S'test_map[e_y][e_x] = 'E'def tm_to_test_map():for line in tm:test_map.append(list(line))def find_path():s_x, s_y = get_start_XY()e_x, e_y = get_end_XY()a_star = A_Star(s_x, s_y, e_x, e_y)a_star.find_path()searched = a_star.get_searched()path = a_star.path#標(biāo)記已搜索區(qū)域mark_searched(searched)#標(biāo)記路徑mark_path(path)print "path length is %d"%(len(path))print "searched squares count is %d"%(len(searched))#標(biāo)記開始、結(jié)束點(diǎn)mark_start_end(s_x, s_y, e_x, e_y)if __name__ == "__main__":#把字符串轉(zhuǎn)成列表tm_to_test_map()find_path()print_test_map()
總結(jié)
以上是生活随笔為你收集整理的用python实现基本A*算法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于Python编程的一些问答
- 下一篇: Python温故