A* 是一种在平面图形上计算最优路径的方法,通常用来做 2D 游戏的最短寻路,该算法是一种 Dijkstra 算法的变体,使用启发式的策略来提高算法的搜索效率。

wikipediamediumpythonf

基本思想

基于启发式的方法,基于BFS去做最短路径搜索,借助类似Manhattan距离作为启发,每次加入后续的多个点,然后按照后续点的属性去排序,不断的加入close的区间,直到第一次遍历到终点就是最短的路径。

$$ f(n) = g(n) + h(n) $$

f代表的是经过当前点,从起点到最终点的距离,g代表的是从起点到当前节点的距离,h代表的是启发式方法到终点的距离。

维护三个list:open(候选列表)、close(状态确定的列表)、children(等待操作的列表)

首先用 bfs 的方法,找到当前节点的可达后续节点,将这些节点加入 children,确定 child 不在 close_list 中后(即在 open 中)则判断哪个是最优解,然后更新 openlist 和 closelist 。

即:每次遍历的当前节点都从 open 中总距离最小的选,然后放入 close。直到 openlist 为空。

相关类别定义

python
class node(): 
    def __init__(self, parent=None, position=None):
        self.parent = parent
        self.position = position

        self.g = 0 
        self.h = 0 
        self.f = 0 
    
    def __eq__(self, o: object) -> bool:
        return o.position == self.position

具体代码实现

python
def asterS(map,slope,start,end):
    # 在astar算法的基础上,我们需要加入的是高度的约束
    # 阻碍的条件是高度不同同时没有slope的存在,这种就是wall
    # 其余的和Astar算法应该是一样的

    # init the start and end node
    start_node = node(None,start)
    end_node = node(None,end)

    # init the open and closed lists
    open_list = []
    close_list = []

    # add the start node to the open list
    open_list.append(start_node)

    # loop util find the end_node 
    while len(open_list)>0:
        # make the best node as current_node
        # init 1 
        current_node = open_list[0]
        current_index = 0
        for index, nod in enumerate(open_list):
            if nod.f<current_node.f:
                current_node = nod
                current_index = index
        
        # pop the curr node off open list, add to close list 
        open_list.pop(current_index)
        close_list.append(current_node)

        # terminal conditions (reach the end node )
        if current_node == end_node:
            path = []
            while(current_node is not None):
                path.append(current_node.position)
                current_node = current_node.parent

            # return the path we find.
            return path[::-1]
        
        # the body of the loop: update the nodes
        # find the available children nodes
        children = []

        # define the adjacent squares 
        positions = [(-1,0),(1,0),(0,-1),(0,1)]
        for pos in positions:
            # get childrens positions
            node_pos = (current_node.position[0]+pos[0], current_node.position[1]+pos[1])
            
            # make sure within range  
            if node_pos[0] < 0 or node_pos[0] >= map.shape[0] or node_pos[1] < 0 or node_pos[1] >= map.shape[1]:
                continue

            # mkae sure walkab
            mapflag = map[current_node.position[0], current_node.position[1]] != map[node_pos[0], node_pos[1]]
            slopeflag1 = slope[node_pos[0], node_pos[1]] == 0  or slope[current_node.position[0], current_node.position[1]] == 0
            slpopeflag2 = slope[node_pos[0], node_pos[1]] != slope[current_node.position[0], current_node.position[1]]
            
            if mapflag and (slopeflag1 or slpopeflag2):
                continue

            # we need creat node first to find out if it is in the openlist or closed list
            new_node = node(current_node, node_pos)
            children.append(new_node)
        
        # loop those children
        # using the __eq__ to judge if it's already traveled.
        for child in children:
            if child in close_list:
                continue
            
            # create f,g,h for the legal child
            child.g = current_node.g + 1 
            child.h = manhattan(child.position, end_node.position)
            child.f = child.g + child.f

            # if the child is already in the open list, compare it
            if child in open_list:
                open_index = open_list.index(child)
                open_node = open_list[open_index] 
                if child.g > open_node.g:
                    continue
                open_list.pop(open_index)
            # if it is not in the open/closelist or better than that in open list, we add it.
            open_list.append(child)