基于搜索的路径规划算法

基于搜索的路径规划算法

——针对A* LPA*算法的理解

1.引言

对于研究系统优化和生产调度相关的课题,掌握对应的路径规划算法是必不可少的能力。移动一个简单的物体看起来是容易的,而路径搜索是复杂的。
根据是否考虑经验法则算法,可以将路径规划算法分为盲目式算法和启发式搜索算法。
解决有向正权值图类规划的Dijkstra算法,可以计算含有负权的Ford算法,系统检查图中所有节点的广度优先算法等等,都属于盲目式算法。这类算法的特点是,可以找到最优路径或最短路径,尽管搜索过程过于盲目即耗费的时间长,搜索的范围广。
启发式搜索利用启发性信息进行制导的搜索,利用启发函数来导航,有效避免了传统方法盲目搜索带来的弊端。典型的算法比如最佳优先算法和贪婪算法。贪婪算法总是做出当前看来最好的选择,也就是说,不从整体最优上加以考虑,只做出在某种意义上的局部最优解。
这种贪心策略的选择,使得大部分启发式路径规划算法只能找到解决问题的近似解而非最优解。
我们需要算法有方向地进行扩散,即启发式搜索,另一方面我们需要得到尽可能最短的路径,于是A就诞生了, 它结合了传统方法Dijkstra和启发式算法的优点,以从起点到该点的距离加上该点到终点的估计距离之和作为该点在图中的优先级,避免了盲目搜索带来大量不必要的浪费,也能够得到问题的最优解。 但在实际应用中,路径常常是动态变化的,一些移动障碍物的存在使得路径规划变得复杂,用上述的算法解决这种问题就变得复杂。2004年,Koenig和Likhachev提出了LPA***

算法(Lifelong Planning A),被用来处理动态环境下从给定起始点到给定目标点的最短路径问题,但在行走过程中,环境中的节点发生变化,想获得新的路径,就得以机器人当前节点为起始点,重新用LPA算法解算,降低了算法的计算效率,增加了时间复杂度。针对该情况,Koenig和Likhachev随后又提出了D Lite算法。与 LPA 采用的正向搜索算法不同,D* Lite 采用反向搜索方式,效果与D* 算法相当,能够很好的适用于未知环境做路经规划,由于其增量规划的思想,它可以做到较少重规划次数以及较少的重规划影响节点数。
在以上各种适用范围不同,复杂度不同的算法中,我着重分析理解了两种算法:其一是A算法,集启发式搜素和Dijkstra算法优点于一身,不仅效率高还可以找出最佳路径。另外一种是较为复杂的LPA算法,其灵活性更高,可以解决移动障碍的问题,在实际应用中更为广泛。

2.A*算法

2.1算法详解
一般的搜索策略都可以归纳为初始化队列,而后重复执行取队列头值并检测,拓展队列,增加候选点的算法执行过程。A算法略微不同的地方就在于添加了启发式函数:F = G + H,
这里,G 为从起点 A 移动到指定方格的移动代价,沿着到达该方格而生成的路径,H 为从指定的方格移动到终点 B 的估算成本。
我们假设存在一张方格地图,上面有白色和黑色的方格,白色表示该路径可以行走,黑色表示障碍,不能通过,行走一步的范围是当前所在节点的八领域(排除障碍和close list中存在的节点)于是设定G:行走代价的值;对于H,有很多方法可以估算 H 值,这里我们使用 Manhattan 方法,计算从当前方格横向或纵向移动到达目标所经过的方格数,忽略对角移动,然后把总数乘以 10 。我们给定左下角为起点,右上角为终点,要求找到一条从起点出发到达终点的路径,且要求路径最短。
首先我们需要建立两个列表,open list和close list。Open list用来存放当前待检查的节点,close list中存放已形成的路径节点集合。从起点出发,我们把起点放入open list。此时open list中只有一个节点,我们别无选择,将它放入close list中,与此同时,起点周围能够行走的点加入到open list中,通过启发式函数计算每个节点F值,选取其中最小的一个放入close list当中。然后检查所有与它相邻的方格,忽略其中在 close list 中或是不可走的方格 ,如果方格不在open list 中,则把它们加入到 open list 中。把我们选定的方格设置为这些新加入的方格的父亲。如果某个相邻的方格已经在 open list 中,则检查这条路径是否更优,也就是说经由当前方格 ( 我们选中的方格 ) 到达那个方格是否具有更小的 G 值。如果没有,不做任何操作。相反,如果 G 值更小,则把那个方格的父亲设为当前方格 ( 我们选中的方格 ) ,然后重新计算那个方格的 F 值和 G 值。不断重复这个过程,直至终点被加入open list中,此时close list当中的节点就是我们找到的最短路径。
2.2算法实现
下图是A
寻路算法的一个简单实现。黑色方格代表障碍,要求找到一条从左下角出发,到达右上角的最短路径。行走区域为当前节点的四领域范围。红色方格为寻找到的路径。代码详见附录。

3.LPA*算法

3.1两类算法比较

A,LPA都可以用于静态环境下移动机器人的路径规划,此时二者计算效率相差不大,都利用了启发式搜索来提高效率,LPA的增量式搜索在这时没有任何帮助。但对于动态环境的路径规划,A算法却有心无力,对于动态环境下进行二次搜索,LPA效率明显高于A
3.2 LPA算法详解
LPA
算法是一种增量启发式搜索版本的A算法,它常被用来处理动态环境下从给定起始点到给定目标点的最短路径问题。启发式搜索和增量式搜索的区别在于,启发式搜索是利用启发函数来对搜索进行指导,从而实现高效的搜索,启发式搜索是一种“智能”搜索。增量搜索是对以前的搜索结果信息进行再利用来实现高效搜索,大大减少搜索范围和时间。
和A
算法一样,LPA算法采用非负、一致性的启发函数H,表示当前位置网格点到目标点距离的估计,沿用了open list来表示被搜索网格点的集合。引入了g(s),g(s)和rhs(s)(s为当前节点,s‘为当前节点的子节点):g*(s)表示起点到当前s点的最短路径距离,g(s)是对g*(s)的值的估计,当前s点的g*(s)值为:s点与某子节点s’的移动代价与该子节点g*(s’)之和序列中的最小值。rhs(s)被定义为s点与某子节点s’的移动代价与该子节点g(s’)之和序列中的最小值。当环境没有发生变化时,g(s)=rhs(s)=A+B,此时s点为局部一致收敛。如果全局的每个节点都满足g(s)=rhs(s),此时LPA算法退化为A算法,不是我们的重点研究范围。当环境发生变化时,边缘代价函数变为无穷大,rhs(s)也相应发生了变化,与g(s)的值不一致,此时s点为局部不一致。局部不一致又分为过一致和欠一致。当 g(s)>rhs(s)时,被称为局部过一致,即 s’ 点到 s 点的代价变低,代表网格上障碍物被清除或搜索到一条更短的“捷径”。当 g(s)<rhs(s) 被称为局部欠一致,当s点欠一致时,即s’点到s点的边缘代价函数c值变高,代表着自由网格被障碍物所占据,这时s点的信息需要被重置,这时候就需要重新搜索计算最短路径。
LPA算法核心是利用k(s)来对这些网格点进行排序,代表着优先级队列中网格点选择的优先权。key值用于处理局部不一致的网格点s,有两个元素组成:k1(s)和k2(s)。k1(s)是g(s)和rhs(s)中的小值与启发函数H的和,k2(s)是g(s)和rhs(s)中的小值。比较key值大小,优先比较k1(s),当k1(s)相同时,在比较k2(s)。key值越小,其优先权越高,该点就越先被搜索。这就是其与A算法不同的地方。LPA重用以前的规划构建过程的一部分(以g值的形式),而不是以更大的内存需求为代价来调整先前规划的优点。特别是,g值不仅可以用来确定最短路径,而且它们比最短路径本身更容易再次使用。**

4.启发式搜索

启发式策略可以通过指导搜索,确定一个最佳的启发式函数,使得搜索过程向最有希望一面发展,降低了时间复杂性。并得到令人能接受的解(虽然大多数情况下很难得到最优解)。
然而,启发式策略是极易出错的。在解决问题的过程中,启发仅仅是下一步将要采取措施的一个猜想,常常根据经验和直觉来判断。由于启发式搜索只有有限的信息,要想预测进一步搜索过程中状态空间的具体行为则很难。一个启发式搜索可能得到一个很好的近似解,也可能一无所获。这是启发式搜索固有的局限性。这种局限性不可能由所谓更好的启发式策略或更有效的搜索算法来消除。一般说来,启发信息越强,扩展的无用节点就越少。引入强的启发信息,有可能大大降低搜索工作量,但不能保证找到最小耗散值的解路径(最佳路径)。因此,在实际应用中,最好能引入降低搜索工作量的启发信息而不牺牲找到最佳路径的保证。

5.附录


from tkinter import *
import enum
import time
import _thread
class PointState(enum.Enum):
    BARRIER = 'black'
    UNUSED = 'white'
    TRAVERSED = 'yellow'
    PATH = 'red'
class MiniMap:
    class Point:
        def __init__(self, x, y, state, rectangle):
            self.x = x
            self.y = y
            self.state = state
            self.rectangle = rectangle
    class Vertex:
        def __init__(self, x, y, f, g, father):
            self.x = x
            self.y = y
            self.f = f
            self.g = g
            self.father = father

    def __init__(self, *args, **kwargs):
        self.row = args[0]
        self.column = args[1]
        self.size = args[2]
        self.start = args[3]
        self.end = args[4]
        self.delay = args[5]
        self.root = Tk()
        self.root.title("A*")
        self.canva = Canvas(self.root, width=self.column * self.size + 3, height=self.row * self.size + 3)
        self.points = self.generatePoints()  # 生成点集
        self.generateMesh()  # 生成网格

        self.canva.bind("<Button-1>", self.createBarrier)
        self.canva.bind("<Button-2>", self.cleanMap)
        self.canva.bind("<Button-3>", self.navigation)

        self.canva.pack(side=TOP, expand=YES, fill=BOTH)
        self.root.resizable(0, 0)
        self.root.mainloop()

    def createBarrier(self, event):
        x = int((event.x + 3) / self.size)
        y = int((event.y + 3) / self.size)
        if x < self.column and y < self.row:
            if (self.points[x][y].state == PointState.BARRIER.value):
                self.points[x][y].state = PointState.UNUSED.value
                self.canva.itemconfig(self.points[x][y].rectangle, fill=self.points[x][y].state)
            else:
                self.points[x][y].state = PointState.BARRIER.value
                self.canva.itemconfig(self.points[x][y].rectangle, fill=self.points[x][y].state)

    def navigation(self, event):
        _thread.start_new_thread(self.generateMap, (self.start, self.end))

    def cleanMap(self, event):
        # 清空画布
        for i in range(self.column):
            for j in range(self.row):
                if (self.points[i][j].state != PointState.BARRIER.value):
                    self.points[i][j].state = PointState.UNUSED.value
                    self.canva.itemconfig(self.points[i][j].rectangle, fill=self.points[i][j].state)

    def generateMap(self, start, end):
        x1 = start[0]
        y1 = start[1]
        x2 = end[0]
        y2 = end[1]
        dictopen = {}
        dictclose = {}
        dictopen[(x1, y1)] = self.Vertex(x1, y1, 10 * (abs(x2 - x1) + abs(y2 - y1)), 0, None)
        # 寻路循环
        while 1:
            fMin = -1
            pMin = None
            for v in dictopen.keys():
                if fMin < 0 or dictopen[v].f <= fMin:
                    fMin = dictopen[v].f
                    pMin = v
            dictclose[pMin] = dictopen[pMin]
            del dictopen[pMin]
            # 检测上方点
            if (pMin[1] > 0):
                pNew = (pMin[0], pMin[1] - 1)
                if (pNew not in dictclose.keys() and self.points[pNew[0]][pNew[1]].state != PointState.BARRIER.value):
                    if (pNew in dictopen.keys()):
                        if ((10 + dictclose[pMin].g) < dictopen[pNew].g):
                            dictopen[pNew].g = 10 + dictclose[pMin].g
                            dictopen[pNew].f = dictopen[pNew].g + 10 * (abs(x2 - pNew[0]) + abs(y2 - pNew[1]))
                            dictopen[pNew].father = pMin
                    else:
                        dictopen[pNew] = self.Vertex(pNew[0], pNew[1], dictclose[pMin].g + 10 + 10 * (
                                    abs(x2 - pNew[0]) + abs(y2 - pNew[1])), dictclose[pMin].g + 10, pMin)
                        self.points[pNew[0]][pNew[1]].state = PointState.TRAVERSED.value
                        self.canva.itemconfig(self.points[pNew[0]][pNew[1]].rectangle,
                                              fill=self.points[pNew[0]][pNew[1]].state)
            # 检测右侧点
            if (pMin[0] < self.column - 1):
                pNew = (pMin[0] + 1, pMin[1])
                if (pNew not in dictclose.keys() and self.points[pNew[0]][pNew[1]].state != PointState.BARRIER.value):
                    if (pNew in dictopen.keys()):
                        if ((10 + dictclose[pMin].g) < dictopen[pNew].g):
                            dictopen[pNew].g = 10 + dictclose[pMin].g
                            dictopen[pNew].f = dictopen[pNew].g + 10 * (abs(x2 - pNew[0]) + abs(y2 - pNew[1]))
                            dictopen[pNew].father = pMin
                    else:
                        dictopen[pNew] = self.Vertex(pNew[0], pNew[1], dictclose[pMin].g + 10 + 10 * (
                                    abs(x2 - pNew[0]) + abs(y2 - pNew[1])), dictclose[pMin].g + 10, pMin)
                        self.points[pNew[0]][pNew[1]].state = PointState.TRAVERSED.value
                        self.canva.itemconfig(self.points[pNew[0]][pNew[1]].rectangle,
                                              fill=self.points[pNew[0]][pNew[1]].state)
            # 检测下方点
            if (pMin[1] < self.row - 1):
                pNew = (pMin[0], pMin[1] + 1)
                if (pNew not in dictclose.keys() and self.points[pNew[0]][pNew[1]].state != PointState.BARRIER.value):
                    if (pNew in dictopen.keys()):
                        if ((10 + dictclose[pMin].g) < dictopen[pNew].g):
                            dictopen[pNew].g = 10 + dictclose[pMin].g
                            dictopen[pNew].f = dictopen[pNew].g + 10 * (abs(x2 - pNew[0]) + abs(y2 - pNew[1]))
                            dictopen[pNew].father = pMin
                    else:
                        dictopen[pNew] = self.Vertex(pNew[0], pNew[1], dictclose[pMin].g + 10 + 10 * (
                                    abs(x2 - pNew[0]) + abs(y2 - pNew[1])), dictclose[pMin].g + 10, pMin)
                        self.points[pNew[0]][pNew[1]].state = PointState.TRAVERSED.value
                        self.canva.itemconfig(self.points[pNew[0]][pNew[1]].rectangle,
                                              fill=self.points[pNew[0]][pNew[1]].state)
            # 检测左侧点
            if (pMin[0] > 0):
                pNew = (pMin[0] - 1, pMin[1])
                if (pNew not in dictclose.keys() and self.points[pNew[0]][pNew[1]].state != PointState.BARRIER.value):
                    if (pNew in dictopen.keys()):
                        if ((10 + dictclose[pMin].g) < dictopen[pNew].g):
                            dictopen[pNew].g = 10 + dictclose[pMin].g
                            dictopen[pNew].f = dictopen[pNew].g + 10 * (abs(x2 - pNew[0]) + abs(y2 - pNew[1]))
                            dictopen[pNew].father = pMin
                    else:
                        dictopen[pNew] = self.Vertex(pNew[0], pNew[1], dictclose[pMin].g + 10 + 10 * (
                                    abs(x2 - pNew[0]) + abs(y2 - pNew[1])), dictclose[pMin].g + 10, pMin)
                        self.points[pNew[0]][pNew[1]].state = PointState.TRAVERSED.value
                        self.canva.itemconfig(self.points[pNew[0]][pNew[1]].rectangle,
                                              fill=self.points[pNew[0]][pNew[1]].state)

            # 检测是否终止
            if ((x2, y2) in dictopen.keys()):
                self.points[x2][y2].state = PointState.PATH.value
                self.canva.itemconfig(self.points[x2][y2].rectangle, fill=self.points[x2][y2].state)
                pNext = dictopen[(x2, y2)].father
                while dictclose[pNext].father in dictclose.keys():
                    self.points[pNext[0]][pNext[1]].state = PointState.PATH.value
                    self.canva.itemconfig(self.points[pNext[0]][pNext[1]].rectangle,
                                          fill=self.points[pNext[0]][pNext[1]].state)
                    pNext = dictclose[pNext].father
                self.points[pNext[0]][pNext[1]].state = PointState.PATH.value
                self.canva.itemconfig(self.points[pNext[0]][pNext[1]].rectangle,
                                      fill=self.points[pNext[0]][pNext[1]].state)
                break
            if (len(dictopen) == 0):
                print('Unreachable!')
                break
            # 等待绘制
            time.sleep(self.delay)

    def generatePoints(self):
        points = [[self.Point(i, j, PointState.UNUSED.value,
                              self.canva.create_rectangle((i * self.size + 3, j * self.size + 3),
                                                          ((i + 1) * self.size + 3, (j + 1) * self.size + 3),
                                                          fill=PointState.UNUSED.value)) for j in range(self.row)] for i
                  in range(self.column)]
        return points

    def generateMesh(self):
        for i in range(self.row + 1):
            self.canva.create_line((3, i * self.size + 3), (self.column * self.size + 3, i * self.size + 3))
        for i in range(self.column + 1):
            self.canva.create_line((i * self.size + 3, 3), (i * self.size + 3, self.row * self.size + 3))

#参数为行数,列数,方格尺寸,起点坐标,终点坐标,延迟时间
demo = MiniMap(6, 6, 80, (5, 0), (0, 5), 0.02)

6.参考

https://blog.csdn.net/Nomuo/article/details/108519341?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-5.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-5.channel_param

  • 2
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值