Python 中的图:A* 算法

1 什么是A*算法

  假设一个走迷宫游戏,我将 A 点定义成起点也就是开始状态,定义 B 为终点也就是结束状态。我们的目标就是找到从 A 走到 B 地最佳路径,如下图所示:

在这里插入图片描述

我们可以将上述的迷宫看做一张图,在简单的情况下(比如这个),生成的图由少量节点和边组成,BFS、DFS 和 Dijkstra 就足够了。然而,在现实生活中,因为我们通常要处理组合复杂性非常大的问题,我们将不得不处理大量的节点和边,而 BFS、DFS 和 Dijkstra 要不可避免地遍历整张图,搜索代价十分巨大。因此,我们必须使用某种意义上的引导算法(启发式算法)。A* 算法就是一种启发式算法。与其他图遍历算法不同,A* 仅根据其功能在看起来有希望且合理的情况下执行一个步骤。它朝着目标前进,不考虑任何非最佳步骤,所以 A* 所搜索出来的路径一定是最优路径。这使得 A* 对于人工智能系统非常有用——尤其是在机器学习和游戏开发中,因为这些系统复制了现实世界的场景。

1.1 A* 的基本概念

  A* 基于使用 启发式 方法来实现最优性和完整性,是最佳优先算法的一种变体。当搜索算法具有最优性时,这意味着它可以保证找到可能的最佳解决方案。当搜索算法具有完备性时,这意味着如果给定问题的解决方案存在,则该算法保证找到它。
  每次 A* 进入一个状态即图中的一个节点,它计算从当前节点移动到所有相邻节点的成本 f(n) ,然后进入具有最低 f(n) 的节点,注意 n 指的是当前节点的邻居节点。计算公式如下:
f ( n ) = g ( n ) + h ( n ) f(n) = g(n) + h(n) f(n)=g(n)+h(n)
其中:

  • f(n):从初始状态经由状态 n 到目标状态的代价估计;
  • g(n):在状态空间中从初始状态到当前状态 n 的实际代价;
  • h(n):从当前状态 n 到目标状态的最佳路径的估计代价。

为了能够重建任何路径,我们需要用具有最佳 f(n) 值的相对标记每个节点。这也意味着如果我们重新访问某些节点,我们也必须更新它们的最佳邻居。A* 的效率高度依赖于启发式值h(n),并且根据问题的类型,我们可能需要使用不同的启发式函数来找到最佳解决方案。。

1.2 启发函数的可接受性和一致性

  如果一个给定的启发式函数从不高估 n 和目标节点之间的实际距离,则 h(n) 是可接受的。因此,对于每个节点 n,适用以下公式:
h ( n ) ≤ h ∗ ( n ) h(n) \leq h^*(n) h(n)h(n)
h*(n)n 和目标节点之间的实际距离。但是,如果函数确实高估了实际距离,但从不超过 d,我们可以肯定地说,该函数产生的解的精度为 d(即它不会高估从开始到结束的最短路径超过 d)。
  如果估计总是小于或等于目标节点 n 和其任何邻居之间的估计距离,加上到达该邻居的估计成本,则给定启发式函数h(n) 是一致的:
c ( n , m ) + h ( m ) ≥ h ( n ) c(n, m) + h(m) \geq h(n) c(n,m)+h(m)h(n)
如果 h(n) 是一致的,那么我们就知道到任何已经检查过的节点的最佳路径。这意味着这个函数是最优的。

2 Python 实现 A* 算法

  本次实现从零开始,也即先构建一个加权有向图,其次利用 A* 算法寻找图中的某一节点到达另一节点的的最佳路径,其中:

  • 图的存储结构是邻接列表,可参考:Python 中的图:图的存储结构
  • 为了方便,本次定义的启发函数 h(n) = 1,即任何的当前节点到目标节点的最佳路径的估计代价均为1。

图的构建如下:

adjacency_list = {'A' : [('B', 1), ('C', 3), ('D', 7)],
                  'B' : [('D', 5)],
                  'C' : [('D', 12)]}

代码如下:

adjacency_list = {'A' : [('B', 1), ('C', 3), ('D', 7)],
                  'B' : [('D', 5)],
                  'C' : [('D', 12)]}
class Graph() :
    def __init__(self, adjacency):
        self.adjacency = adjacency

    def get_neighbors(self, v):
        return self.adjacency[v]

    def h(self, n):
        H = {'A' : 1,
             'B' : 1,
             'C' : 1,
             'D' : 1}

        return H[n]

    def A_star(self, start, target):
        # 首先说明:下面注释中邻居节点和子节点指的是同一个意思
        # open_list 存的是已访问,但该节点的邻居节点仍未被访问的节点,从 start 节点开始
        # close_list 存的是已访问,且该节点的邻居节点已被访问的节点
        open_list = set(start)
        close_list = set()

        # g 存的是
        g = {}
        g[start] = 0

        # 记录所有节点的父节点
        parents = {}
        parents[start] = start

        while len(open_list) > 0:
            # 当前节点
            n = None

            # 在当前节点的邻居节点中找到 f() 值最低的节点,更新当前节点
            for v in open_list :
                if n == None or g[v] + self.h(v) < g[n] + self.h(n) :
                    n = v

            if n == None :
                print('path does not exists!')
                return None

            # 如果当前节点是目标节点,回溯求得最佳路径。利用 parents
            if n == target :
                path = []
                while parents[n] != n :
                    path.append(n)
                    n = parents[n]

                path.append(start)
                path.reverse()
                best_path = '->'.join(path)
                print(f'the best path of the node {start} to {target} is: {best_path}')
                return path

            # 当前节点的所有邻居节点
            for (m, weight) in self.get_neighbors(n) :
                # 如果没被访问过,添加至 open_list
                if m not in open_list and m not in close_list :
                    open_list.add(m)
                    parents[m] = n
                    # 开始节点到当前节点的邻居节点的代价,为求下一个最佳节点做准备
                    g[m] = g[n] + weight
                # 否则说明 m 节点的父节点是 n 节点的子节点,也是最佳路径中的上一个节点的子节点
                else :
                    # 如果从上一个节点先访问 n 再访问 m 比直接访问 m 更快,则更新父节点和相应的代价
                    if g[m] > g[n] + weight :
                        g[m] = g[n] + weight
                        parents[m] = n
                        # 如果 m 节点位于 closed_list 中,将其移至 open_list
                        if m in close_list :
                            close_list.remove(m)
                            open_list.add(m)

            open_list.remove(n)
            close_list.add(n)

        print('path does not exists!')
        return None

graph = Graph(adjacency_list)
graph.A_star('A', 'D')

代码中有详细注释,便不再讲解。效果如下:
在这里插入图片描述

  • 3
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
在这个实验,我们将使用A*算法来解决迷宫寻路问题。A*算法是一种启发式搜索算法,它在找到最短路径时具有较高的效率和准确性。以下是这个实验的一般步骤: 1. 首先,我们需要定义一个迷宫。它可以是一个方形的矩阵,用数字表示每个位置的状态。比如,0表示可以通过的空间,1表示墙壁,2表示起点,3表示终点等等。 2. 接下来,我们需要定义一个节点类。每个节点都有一个x和y坐标,以及一个G值表示从起点到该节点的距离,一个H值表示从该节点到终点的估计距离,一个F值表示G和H的和。我们将使用这个F值来选择下一个扩展节点。 3. 接着,我们需要实现A*算法的主要函数。我们将从起点开始,计算起点到每个可以到达的节点的G和H值,并将它们添加到一个开放列表。然后,我们选择F值最小的节点进行扩展,更新它周围节点的G和H值,并将它们添加到开放列表。我们一直执行这个操作,直到终点被添加到开放列表或者开放列表为空。如果终点被添加到开放列表,那么我们就找到了最短路径,并可以通过逆向追溯每个节点的父节点来得到路径。如果开放列表为空,那么说明无法找到路径。 4. 最后,我们将实现一个可视化界面,用于展示迷宫和最短路径。可以使用Python的Pygame库来实现这个界面。 这里只是一个大致步骤,具体实现还需要根据实际情况进行调整和修改。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

八岁爱玩耍

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

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

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

打赏作者

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

抵扣说明:

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

余额充值