堆優(yōu)化的A*算法-Python實(shí)現(xiàn)

堆優(yōu)化的A*算法-Python實(shí)現(xiàn)

A*算法解決二維網(wǎng)格地圖中的尋路問題

  • 輸入:圖片(白色區(qū)域代表可行,深色區(qū)域代表不行可行)
  • 輸出:路徑(在圖中繪制)
""" 方格地圖中的A*算法 (openList進(jìn)行了堆優(yōu)化)
A* 算法:  F = G+H
F: 總移動(dòng)代價(jià)
G: 起點(diǎn)到當(dāng)前點(diǎn)的移動(dòng)代價(jià)  直:1, 斜:1.4
H: 當(dāng)前點(diǎn)到終點(diǎn)的預(yù)估代價(jià)  曼哈頓距離
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1.把起點(diǎn)加入 openList中
2.While True:
    a.遍歷openList横媚,查找F值最小的節(jié)點(diǎn)泥技,作為current
    b.current是終點(diǎn):
        ========結(jié)束========
    c.從openList中彈出慕购,放入closeList中
    d.對(duì)八個(gè)方位的格點(diǎn):
        if 越界 or 是障礙物 or 在closeList中:
            continue
        if 不在openList中:
            設(shè)置父節(jié)點(diǎn),F,G,H
            加入openList中
        else:
            if 這條路徑更好:
                設(shè)置父節(jié)點(diǎn),F,G
                更新openList中的對(duì)應(yīng)節(jié)點(diǎn)
3.生成路徑path
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
堆優(yōu)化:
    openList:作為最小堆,按F值排序存儲(chǔ)坐標(biāo) (不更新只增加)
    openDict:坐標(biāo):點(diǎn)詳細(xì)信息 (既更新又增加)
    get_minfNode() 從openList中彈出坐標(biāo),去openDict中取點(diǎn) (但由于不更新只增加烟号,坐標(biāo)可能冗余)
    in_openList() 判斷坐標(biāo)是否在openDict中即可 

"""
import math
from PIL import Image,ImageDraw 
import numpy as np
import heapq # 堆

STAT_OBSTACLE='#'
STAT_NORMAL='.'

def manhattan(x1,y1, x2,y2):
    """兩個(gè)Point的曼哈頓距離"""
    h = abs(x1-x2)+abs(y1-y2)
    return h

class Node():
    """
    開放列表和關(guān)閉列表的元素類型坯认,parent用來在成功的時(shí)候回溯路徑
    """
    def __init__(self, x, y,parent=None, g=0, h=0):
        self.parent = parent
        self.x = x
        self.y = y
        self.g = g
        self.h = h
        self.update()
    
    def update(self):
        self.f = self.g+self.h


class A_Star:
    """ x是行索引翻擒,y是列索引
    """
    def __init__(self, test_map, start=None, end=None):
        """地圖,起點(diǎn)牛哺,終點(diǎn)"""
        self.map = test_map
        self.cols = len(test_map[0])
        self.rows = len(test_map)
        self.s_x, self.s_y = start if start else [0,0]
        self.e_x, self.e_y = end if end else [self.rows-1,self.cols-1]
        self.closeList = set()
        self.path = []
        self.openList = []  # 堆陋气,只添加,和彈出最小值點(diǎn)引润,
        self.openDict = dict() # openList中的 坐標(biāo):詳細(xì)信息 -->不冗余的
        
    
    def find_path(self):
        """A*算法尋路主程序"""
        p = Node(self.s_x, self.s_y, 
                 h=manhattan(self.s_x,self.s_y, self.e_x,self.e_y)) # 構(gòu)建開始節(jié)點(diǎn)
        heapq.heappush(self.openList, (p.f,(p.x,p.y)))
        
        self.openDict[(p.x,p.y)] = p  # 加進(jìn)dict目錄
        while True:
            current = self.get_minfNode()
            if current.x==self.e_x and current.y==self.e_y:
                print('find path')
                self.make_path(current)
                break
            
            self.closeList.add((current.x,current.y))  ## 加入closeList
            del self.openDict[(current.x,current.y)]
            self.extend_surrounds(current) # 會(huì)更新close list

    def make_path(self,p):
        """從結(jié)束點(diǎn)回溯到開始點(diǎn)巩趁,開始點(diǎn)的parent==None"""
        while p:
            self.path.append((p.x, p.y))
            p = p.parent
    
    def extend_surrounds(self, node):
        """ 將當(dāng)前點(diǎn)周圍可走的點(diǎn)加到openList中,
            其中 不在openList中的點(diǎn) 設(shè)置parent淳附、F,G,H 加進(jìn)去议慰,
                 在openList中的點(diǎn)  更新parent、F,G,H
        """
        motion_direction = [[1, 0], [0,  1], [-1, 0], [0,  -1], 
                            [1, 1], [1, -1], [-1, 1], [-1, -1]]  
        for dx, dy in motion_direction:
            x,y = node.x+dx, node.y+dy
            new_node = Node(x,y)
            # 位置無效奴曙,或者是障礙物, 或者已經(jīng)在closeList中 
            if not self.is_valid_xy(x,y) or not self.not_obstacle(x,y) or self.in_closeList(new_node): 
                continue
            if abs(dx)+abs(dy)==2:  ## 斜向
                h_x,h_y = node.x+dx,node.y # 水平向
                v_x,v_y = node.x,node.y+dy # 垂直向
                if not self.is_valid_xy(h_x,h_y) or not self.not_obstacle(h_x,h_y) or self.in_closeList(Node(h_x,h_y)): 
                    continue
                if not self.is_valid_xy(v_x,v_y) or not self.not_obstacle(v_x,v_y) or self.in_closeList(Node(v_x,v_y)): 
                    continue
            #============ ** 關(guān)鍵 **             ========================
            #============ 不在openList中别凹,加進(jìn)去; ========================
            #============ 在openList中洽糟,更新      ========================
            #============對(duì)于openList和openDict來說炉菲,操作都一樣 ===========
            new_g = node.g + self.cal_deltaG(node.x,node.y, x,y)
            sign=False # 是否執(zhí)行操作的標(biāo)志 
            if not self.in_openList(new_node): # 不在openList中
                # 加進(jìn)來,設(shè)置 父節(jié)點(diǎn), F, G, H
                new_node.h = self.cal_H(new_node)
                sign=True
            elif self.openDict[(new_node.x,new_node.y)].g > new_g: # 已在openList中坤溃,但現(xiàn)在的路徑更好
                sign=True
            if sign:
                new_node.parent = node
                new_node.g = new_g
                new_node.f = self.cal_F(new_node)
                self.openDict[(new_node.x,new_node.y)]=new_node # 更新dict目錄
                heapq.heappush(self.openList, (new_node.f,(new_node.x,new_node.y)))
        
    def get_minfNode(self):
        """從openList中取F=G+H值最小的 (堆-O(1))"""
        while True:
            f, best_xy=heapq.heappop(self.openList)
            if best_xy in self.openDict:
                return self.openDict[best_xy]

    def in_closeList(self, node):
        """判斷是否在closeList中 (集合-O(1)) """
        return True if (node.x,node.y) in self.closeList else False
     
    def in_openList(self, node):
        """判斷是否在openList中 (字典-O(1))"""
        if not (node.x,node.y) in self.openDict:
            return False
        else:
            return True

    def is_valid_xy(self, x,y):
        if x < 0 or x >= self.rows or y < 0 or y >= self.cols:
            return False
        return True
        
    def not_obstacle(self,x,y):
        return self.map[x][y] != STAT_OBSTACLE
    
    def cal_deltaG(self,x1,y1,x2,y2):
        """ 計(jì)算兩點(diǎn)之間行走的代價(jià)
            (為簡化計(jì)算)上下左右直走拍霜,代價(jià)為1.0,斜走薪介,代價(jià)為1.4  G值
        """
        if x1 == x2 or y1 == y2:
            return 1.0
        return 1.4
    
    def cal_H(self, node):
        """ 曼哈頓距離 估計(jì)距離目標(biāo)點(diǎn)的距離"""
        return abs(node.x-self.e_x)+abs(node.y-self.e_y) # 剩余路徑的估計(jì)長度
    
    def cal_F(self, node):
        """ 計(jì)算F值 F = G+H 
            A*算法的精髓:已經(jīng)消耗的代價(jià)G祠饺,和預(yù)估將要消耗的代價(jià)H
        """
        return node.g + node.h


def plot(test_map,path):
    """繪制地圖和路徑
        test_map:二維數(shù)組
        path:路徑坐標(biāo)數(shù)組
    """
    out = []
    for x in range(len(test_map)):
        temp = []
        for y in range(len(test_map[0])):
            if test_map[x][y]==STAT_OBSTACLE:
                temp.append(0)
            elif test_map[x][y]==STAT_NORMAL:
                temp.append(255)
            elif test_map[x][y]=='*':
                temp.append(127)
            else:
                temp.append(255)
        out.append(temp)
    for x,y in path:
        out[x][y] = 127
    out = np.array(out)
    img = Image.fromarray(out)
    img.show()

def path_length(path):
    """計(jì)算路徑長度"""
    l = 0
    for i in range(len(path)-1):
        x1,y1 = path[i]
        x2,y2 = path[i+1]
        if x1 == x2 or y1 == y2:
            l+=1.0
        else:
            l+=1.4
    return l
    

def img_to_map(img_file):
    """地圖圖片變二維數(shù)組"""
    test_map = []
    img = Image.open(img_file)
    img = img.resize((100,100))  ### resize圖片尺寸
    img_gray = img.convert('L')  # 地圖灰度化
    img_arr = np.array(img_gray)
    img_binary = np.where(img_arr<127,0,255)
    for x in range(img_binary.shape[0]):
        temp_row = []
        for y in range(img_binary.shape[1]):
            status = STAT_OBSTACLE if img_binary[x,y]==0 else STAT_NORMAL 
            temp_row.append(status)
        test_map.append(temp_row)
    
    return test_map

# ===== test case ===============
test_map=img_to_map('map_2.bmp')
a = A_Star(test_map)
a.find_path()
plot(test_map,a.path)
print('path length:',path_length(a.path))

測(cè)試用例及結(jié)果

map1.png
map2.png
map5.png

存在的問題

不確定是否是最優(yōu)路徑
原文描述:
“ If we overestimate this distance, however, it is not guaranteed to give us the shortest path. In such cases, we have what is called an "inadmissible heuristic.".

Technically, in this example, the Manhattan method is inadmissible because it slightly overestimates the remaining distance.”
即如果我們高估了H,則不能保證最短路徑昭灵。而曼哈頓距離略微高估了吠裆。

另外伐谈,筆者不確定程序是不是正確,以及是不是真正的A*算法试疙,請(qǐng)大神們指正诵棵。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市祝旷,隨后出現(xiàn)的幾起案子履澳,更是在濱河造成了極大的恐慌,老刑警劉巖怀跛,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件距贷,死亡現(xiàn)場離奇詭異,居然都是意外死亡吻谋,警方通過查閱死者的電腦和手機(jī)忠蝗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來漓拾,“玉大人阁最,你說我怎么就攤上這事『Я剑” “怎么了速种?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長低千。 經(jīng)常有香客問我配阵,道長,這世上最難降的妖魔是什么示血? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任棋傍,我火速辦了婚禮,結(jié)果婚禮上矾芙,老公的妹妹穿的比我還像新娘舍沙。我一直安慰自己近上,他們只是感情好剔宪,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著壹无,像睡著了一般葱绒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上斗锭,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天地淀,我揣著相機(jī)與錄音,去河邊找鬼岖是。 笑死帮毁,一個(gè)胖子當(dāng)著我的面吹牛实苞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播烈疚,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼黔牵,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了爷肝?” 一聲冷哼從身側(cè)響起猾浦,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎灯抛,沒想到半個(gè)月后金赦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡对嚼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年夹抗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纵竖。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡兔朦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出磨确,到底是詐尸還是另有隱情沽甥,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布乏奥,位于F島的核電站摆舟,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏邓了。R本人自食惡果不足惜恨诱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望骗炉。 院中可真熱鬧照宝,春花似錦、人聲如沸句葵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乍丈。三九已至剂碴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間轻专,已是汗流浹背忆矛。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留请垛,地道東北人催训。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓洽议,卻偏偏與公主長得像,于是被迫代替她去往敵國和親漫拭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子绞铃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 版權(quán)聲明: 以下內(nèi)容來自微信公共帳號(hào)“EOS技術(shù)愛好者”,搜索“EOSTechLover”即可訂閱嫂侍,翻譯Locha...
    Lochaiching閱讀 2,046評(píng)論 0 1
  • pyspark.sql模塊 模塊上下文 Spark SQL和DataFrames的重要類: pyspark.sql...
    mpro閱讀 9,464評(píng)論 0 13
  • Chapter 1 In the year 1878, I took my degree of Doctor of...
    foxgti閱讀 3,700評(píng)論 0 6
  • Disney has removed James Gunn as director of Guardians of...
    梓冬青閱讀 134評(píng)論 0 0
  • 想想自己搬過來一周了挑宠,一開始搬過來的時(shí)候菲盾,這邊的情況是... (可以點(diǎn)擊下方“看房初況”鏈接查看) 看房初況 雖說...
    笑笑biu閱讀 200評(píng)論 0 0