路径规划——A*算法

路径规划——A*算法

算法原理

为了解决Dijkstra算法效率低的问题,A Star 算法作为一种启发式算法被提出。该算法在广度优先的基础上加入了一个估价函数。如BFS和常规方法如Dijsktra算法结合在一起的算法,有点不同的是,类似BFS的启发式经常给出一个近似解而不是保证最佳解,A Star是路径搜索中最受欢迎的选择,它相当灵活,并且能用于多种多样的情理之中,A Star 潜在的搜索一个很大的区域,A Star算法能用于搜索最短路径,A Star 能用启发式函数引发它自己,在简单的情况中,它和BFS一样快。
BFS算法:Best First Search
Dijkstra算法:Dijkstra

1968年,启发式方法提出了A* ,其中包括将已知区域划分为单个单元,并计算每个单元达到目标的总成本。给定从起点到当前分析像元的总距离以及从分析像元到目标的距离,总路径成本使用以下公式计算:
F ( n ) = G ( n ) + H ( n ) , ( 1 ) F ( n ) 是总路径开销,也是节点 n 的综合优先级。每次选择下一个要访问的节点时, 总是选取综合优先级最高的节点,也就是总开销最小的节点; G ( n ) 是从起点到达指定单元的成本和; H ( n ) 是从指定单元到达目标的预估成本,也是 A ∗ 算法的启发函数。 F(n)=G(n)+H(n), \quad(1)\\ F(n)是总路径开销,也是节点n的综合优先级。每次选择下一个要访问的节点时,\\总是选取综合优先级最高的节点,也就是总开销最小的节点;\\ G(n)是从起点到达指定单元的成本和;\\ H(n)是从指定单元到达目标的预估成本,也是A^*算法的启发函数。 F(n)=G(n)+H(n),(1)F(n)是总路径开销,也是节点n的综合优先级。每次选择下一个要访问的节点时,总是选取综合优先级最高的节点,也就是总开销最小的节点;G(n)是从起点到达指定单元的成本和;H(n)是从指定单元到达目标的预估成本,也是A算法的启发函数。

  • start:路径规划的起始点
  • goal:路径规划的终点
  • g_score:从当前点(current_node)到出发点(start)的移动代价
  • h_score:不考虑障碍物,从当前点(current_node)到目标点的估计移动代价
  • f_score:f_score=g_score+h_score
  • openlist:寻找过程中的待检索节点列表
  • closelist:不需要再次检索的节点列表
  • came_Form:存储父子节点关系的列表,用于回溯最优路径,非必须,也可以通过节点指针实现

A*算法的核心就是上式,在运算过程中,每次从优先队列中选取F(n)值最小的节点作为下一个待访问的节点。

优点:利用启发式函数,搜索范围小,提高了搜索效率;如果最优路径存在,那么一定能找到最优路径。
缺点:A* 算法不适用于动态环境;不适用于高维空间,计算量大;目标点不可时会造成大量性能消耗。

算法流程:
1.从起点start开始,将start放入open优先队列中,open中存储的是将要遍历的节点,列表closed为空,closed是用来存放已经遍历的节点;
2.遍历start,将start从open中移除并添加到closed中,寻找start的邻居节点,至于最多可以找到多少邻居节点取决于算法规划,这里假设只允许节点向上下左右4个方向寻找那便是最多可以找到4个可通行的邻居节点,并计算F(n),将可通行的邻居节点放入open中;
3.从open中选择F(n)值最小的节点node,将node节点从open中移除并添加到closed中,寻找node的可通行的邻居节点并计算F(n);
4.如果邻居节点neighbor已经在closed中了,则跳过,不进行其他处理,
5.如果邻居节点neighbor不在open中,则将其添加到open中,并且将node设置为neighbor的父节点;
6.如果邻居节点neighbor已经在open中了,则计算新的路径从start到neighbor的代价即G(n)值:
如果新的G(n)值比之前存储的值更小则更新,并且将neighbor的父节点改为节点node,并计算F(n),之后存储到open中会根据F(n)值进行排序,
如果新的G(n)值比之前存储的值更大,则说明新的路径代价更高便不会选取这个节点也就不需要做出更新等处理;
7.再次从第3步开始执行,如此循环,直到在第3步中找出的节点node是目标节点goal时结束循环,也就成功找到了路径;或者在执行第3步时open为空,也要结束搜索过程了,此时说明无法找到合适路径。

算法实现

"""
    Filename: a_star.py
    Description: Plan path using A* Algorithm
    Author: Benxiaogu:https://github.com/Benxiaogu
    Date: 2024-08-05
"""
import heapq
import math
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation


class AStar:
    def __init__(self,grid,start,goal,board_size):
        self.grid = grid
        self.start = self.Node(start,0,0)
        self.goal = self.Node(goal,0,0)
        self.board_size = board_size

    class Node:
        def __init__(self, position, g, h, parent=None):
            self.position = position    # position of node
            self.g = g                  # distance from node to start
            self.h = h                  # heuristic value from node to goal
            self.parent = parent        # parent node

        def __eq__(self, other) -> bool:
            return self.position == other.position
        
        def __lt__(self, other):
            # Prioritise nodes based on heuristic value
            return self.g+self.h < other.g+other.h or (self.g+self.h==other.g+other.h and self.h<other.h)
        
    class Neighbor:
        def __init__(self,directions,cost):
            self.directions = directions
            self.cost = cost
    
    def get_neighbors(self, node):
        neighbors = []
        distances = []
        nexts = [self.Neighbor((-1, 0),1), self.Neighbor((0, 1),1), self.Neighbor((0, -1),1), self.Neighbor((1,0),1),
                self.Neighbor((-1,1),math.sqrt(2)), self.Neighbor((1,1),math.sqrt(2)),self.Neighbor((1, -1),math.sqrt(2)), self.Neighbor((-1,-1),math.sqrt(2))]
        for next in nexts:
            neighbor = (node[0] + next.directions[0], node[1] + next.directions[1])
            if self.board_size <= neighbor[0] < len(self.grid)-self.board_size and self.board_size <= neighbor[1] < len(self.grid[0])-self.board_size:
                if self.grid[neighbor[0]][neighbor[1]] == 0:
                    if next.directions[0] != 0 and next.directions[1] != 0:  # 对角线方向
                        if self.grid[node[0] + next.directions[0]][node[1]] == 0 and self.grid[node[0]][node[1] + next.directions[1]] == 0:
                            neighbors.append(neighbor)
                            distances.append(next.cost)
                    else:
                        neighbors.append(neighbor)
                        distances.append(next.cost)

        return neighbors,distances

    def plan(self):
        open = []
        self.searched = [] # Used to record nodes that are searched
        g_cost = {self.start.position:0}

        self.start.h = self.heuristic(self.start.position)
        heapq.heappush(open, self.start)

        while open:
            # Select the node closest to the start node
            current_node = heapq.heappop(open)
            if current_node.position in self.searched:
                continue
            self.searched.append(current_node.position)
            
            # Find the goal
            if current_node == self.goal:
                self.goal = current_node
                self.path = []
                while current_node:
                    self.path.append(current_node.position)
                    current_node = current_node.parent
                self.path = self.path[::-1]
                        
                return self.goal.g
            
            # Find the neighbors of the current node and determine in turn if they have already been closed
            neighbors, distances = self.get_neighbors(current_node.position)
            for neighbor,distance in zip(neighbors,distances):
                if neighbor in self.searched:
                    continue
                h = self.heuristic(neighbor)
                g = current_node.g+distance

                if neighbor not in g_cost or g < g_cost[neighbor]:
                    g_cost[neighbor] = g
                    neighbor_node = self.Node(neighbor,g,h,current_node)
                    heapq.heappush(open,neighbor_node)
                
        return -1

    # def heuristic(self, node):
    #     # Manhattan distance from current node to goal node
    #     return abs(node[0] - self.goal.position[0]) + abs(node[1] - self.goal.position[1])
    
    # def heuristic(self, node):
    #     # Chebyshev Distance
    #     D = 1
    #     D2 = math.sqrt(2)
    #     dx = abs(node[0] - self.goal.position[0])
    #     dy = abs(node[1] - self.goal.position[1])
    #     return D * (dx + dy) + (D2 - 2 * D) * min(dx, dy)
    
    def heuristic(self, node):
        # Euclidean Distance
        D = 1
        dx = abs(node[0] - self.goal.position[0])
        dy = abs(node[1] - self.goal.position[1])
        return D * math.sqrt(dx * dx + dy * dy)

结果:
在这里插入图片描述

代码如下,除了Python还提供了C++版本的,欢迎大家Follow andStar:
https://github.com/Benxiaogu/PathPlanning/tree/main/A_Star

启发式函数

曼哈顿距离
标准的启发式函数是曼哈顿距离(Manhattan distance)。考虑你的代价函数并找到从一个位置移动到邻近位置的最小代价D。因此,地图中的启发式函数应该是曼哈顿距离的D倍,常用于在地图上只能前后左右移动的情况:
h ( n ) = D ∗ ( a b s ( n . x – g o a l . x ) + a b s ( n . y – g o a l . y ) ) h(n) = D * (abs ( n.x – goal.x ) + abs ( n.y – goal.y ) ) h(n)=D(abs(n.xgoal.x)+abs(n.ygoal.y))

function heuristic(node) =
    dx = abs(node.x - goal.x)
    dy = abs(node.y - goal.y)
    return D * (dx + dy)

对角距离:
又称为切比雪夫距离。
对角距离是指在路径方案中允许斜着朝邻近的节点移动时的起点到终点的距离。
对角距离的计算如下:

function heuristic(node) =
    dx = abs(node.x - goal.x)
    dy = abs(node.y - goal.y)
    return D * (dx + dy) + (D2 - 2 * D) * min(dx, dy)

这里的D2指的是两个斜着相邻节点之间的移动代价。如果所有节点都是正方形,则其值等于sqrt(2)*D

欧几里得距离
如果你的单位可以沿着任意角度移动(而不是网格方向),那么你也许应该使用直线距离:
h ( n ) = D ∗ s q r t ( ( n . x − g o a l . x ) 2 + ( n . y − g o a l . y ) 2 ) h(n) = D * sqrt((n.x-goal.x)^2 + (n.y-goal.y)^2) h(n)=Dsqrt((n.xgoal.x)2+(n.ygoal.y)2)

function heuristic(node) =
    dx = abs(node.x - goal.x)
    dy = abs(node.y - goal.y)
    return D * sqrt(dx * dx + dy * dy)

对于网格形式的图,上述启发函数的应用场景:

如果图形中只允许朝上下左右四个方向移动,此时使用曼哈顿距离;
如果图形中允许朝上下左右以及对角线总共八个方向移动,此时使用对角距离;
如果图形中允许朝任何方向移动,此时使用欧几里得距离。

启发函数会影响A Star算法的行为。
1.如果启发函数H(n)始终为0,A Star算法将仅由G(n)决定节点的优先级,此时A Star算法便退化成了Dijkstra算法
2.如果H(n)始终小于等于节点n到终点的实际代价,则A Star算法一定能够找到最短路径。但是当H(n)的值越小,算法将遍历越多的节点,便就导致了算法越慢。(后面将证明)
3.如果H(n)完全等于节点n到终点的实际代价,则A Star算法将能够找到最佳路径,并且速度也很快。不过并非在所有场景下都能够做到这一点。因为在没有达到终点之前,我们很难确切算出距离终点的代价。
4.如果H(n)大于节点n到终点的实际代价,则A Star算法无法保证找到最短路径,不过此时会很快完成运算过程。
5.如果H(n)相较于G(n)大很多,达到一种极端情况,此时H(n)便直接影响算法的结果,也就变成了贪婪最佳优先搜索,不一定找到最短路径。

以上几种均是近似启发式函数,用于预估代价值。

除了近似启发式,也有精确启发式,但是一般情况下获取精确启发式函数值比较占用时间。精确启发式的计算:

1.在运行A*搜索算法之前,预先计算每对单元格之间的距离;
2.如果没有阻塞单元格(障碍物),我们可以使用距离公式/欧几里德距离,在不进行任何预先计算的情况下找到h的精确值。

作为初学者,难免存在不足之处,如果有误,还请指正,谢谢!
一起学习,一起进步!

  • 26
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,以下是 Python 实现 A* 算法的代码: ```python from queue import PriorityQueue class State: def __init__(self, value, parent, start=0, goal=0): self.children = [] self.parent = parent self.value = value self.distance = 0 if parent: self.start = parent.start self.goal = parent.goal self.path = parent.path[:] self.path.append(value) else: self.start = start self.goal = goal self.path = [value] def get_distance(self): pass def create_children(self): pass class State_String(State): def __init__(self, value, parent, start=0, goal=0): super().__init__(value, parent, start, goal) self.distance = self.get_distance() def get_distance(self): if self.value == self.goal: return 0 distance = 0 for i in range(len(self.goal)): letter = self.goal[i] distance += abs(i - self.value.index(letter)) return distance def create_children(self): if not self.children: for i in range(len(self.goal) - 1): val = self.value val = val[:i] + val[i+1] + val[i] + val[i+2:] child = State_String(val, self) self.children.append(child) class AStar_Solver: def __init__(self, start, goal): self.path = [] self.visited_queue = [] self.priority_queue = PriorityQueue() self.start = start self.goal = goal def solve(self): start_state = State_String(self.start, None, self.start, self.goal) count = 0 self.priority_queue.put((0, count, start_state)) while not self.path and self.priority_queue.qsize(): closest_child = self.priority_queue.get()[2] closest_child.create_children() self.visited_queue.append(closest_child.value) for child in closest_child.children: if child.value not in self.visited_queue: count += 1 if not child.distance: self.path = child.path break self.priority_queue.put((child.distance, count, child)) if not self.path: print("Goal of " + self.goal + " is not possible!") return self.path start = "dabcfehg" goal = "abcdefgh" a = AStar_Solver(start, goal) a.solve() print(a.path) ``` 在上述代码中,`State` 类表示状态,包含当前状态值、父状态、起始状态、目标状态、路径和距离等属性。`State_String` 类是 `State` 类的子类,用于处理字符串类型的状态。`get_distance` 函数用于计算当前状态与目标状态之间的距离,`create_children` 函数用于创建当前状态的子状态。 `AStar_Solver` 类是 A* 算法的主要实现部分。在 `__init__` 函数中,我们初始化路径和队列等属性。在 `solve` 函数中,我们首先将起始状态加入优先队列,并不断从队列中取出距离最短的状态,生成其子状态,并将未访问过的子状态加入优先队列中。在每次加入子状态到队列中时,我们使用 `count` 属性来确保优先队列中的元素是按照加入顺序排序的。当找到目标状态时,我们提取路径并返回。 最后,我们通过调用 `AStar_Solver` 类来解决字符串类型的问题。在此例中,我们使用字符串 "dabcfehg" 作为起始状态,使用字符串 "abcdefgh" 作为目标状态。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笨小古

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值