A* 搜索算法

本文介绍了A*算法,包括g(n)、h(n)和f(n)的含义,以及如何通过启发式函数和优先队列进行路径决策。重点讲解了f(n)在算法决策中的作用以及heapq.heappush在优先队列中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

可用于带权图,寻找最短路径

目录

一、介绍

二、代码

三、其他说明

一)g(n)、h(n) 和 f(n)分别代表什么意思?

二)neighbor.heuristic_cost是怎么得到的?

三)上面的代码怎么体现f(n)用于决策?

四)heapq.heappush 将节点添加到优先队列中,优先队列会根据 f(n) 的值自动排序,这是这个函数规定的吗?


一、介绍

A算法是一种高效的路径寻找和图遍历算法,常用于各种领域,如地图导航、游戏编程等。A算法的核心在于它使用了一个评价函数 f(n) 来估算通过节点 n 到达目标节点的最短路径代价。这个评价函数是两个其他函数的和:g(n) 和 h(n)。

  • g(n):从起点到当前节点 n 的实际距离。
  • h(n):当前节点 n 到目标节点的估计距离(启发式估计)。这个估计应该不超过从 n 到目标节点的实际距离。

算法的步骤大致如下:

  1. 将起始节点加入到开放列表(Open List)。
  2. 如果开放列表为空,表示没有路径,算法结束。
  3. 从开放列表中取出 f(n) 值最小的节点 n,将其移动到关闭列表(Closed List)。
  4. 对节点 n 的所有邻居节点进行以下操作:
    1. 如果邻居节点已经在关闭列表中,忽略它。
    2. 如果邻居节点不在开放列表中,将它加入开放列表,并计算 g(n)、h(n) 和 f(n)。设置这个邻居节点的父节点为 n。
    3. 如果邻居节点已经在开放列表中,检查通过 n 到达它的路径是否更好,即是否有更低的 g(n) 值。如果是,更新它的 g(n),f(n),并将其父节点设置为 n。
  5. 重复步骤 2-4,直到目标节点被加入到关闭列表,这时路径被找到,或者开放列表为空,表示没有路径。

二、代码

import heapq

class Node:
    """节点类用于表示图中的每个节点"""
    def __init__(self, name, heuristic_cost=0):
        self.name = name
        self.heuristic_cost = heuristic_cost  # h(n)
        self.parent = None
        self.g = float('inf')  # 路径的实际代价
        self.f = float('inf')  # f(n) = g(n) + h(n)

    def __lt__(self, other):
        return self.f < other.f

def a_star_algorithm(start_node, end_node):
    open_list = []
    closed_list = set()

    start_node.g = 0
    start_node.f = start_node.g + start_node.heuristic_cost

    heapq.heappush(open_list, start_node)

    while open_list:
        current_node = heapq.heappop(open_list)
        closed_list.add(current_node)

        if current_node == end_node:
            return reconstruct_path(end_node)

        for neighbor in current_node.neighbors:
            if neighbor in closed_list:
                continue

            tentative_g_score = current_node.g + get_distance(current_node, neighbor)

            if tentative_g_score < neighbor.g:
                neighbor.parent = current_node
                neighbor.g = tentative_g_score
                neighbor.f = neighbor.g + neighbor.heuristic_cost
                if neighbor not in open_list:
                    heapq.heappush(open_list, neighbor)

    return None  # 如果找不到路径

def reconstruct_path(end_node):
    """重建路径"""
    path = []
    current = end_node
    while current:
        path.append(current.name)
        current = current.parent
    return path[::-1]  # 返回反转的路径

def get_distance(node1, node2):
    # 此函数根据实际应用计算两个节点之间的距离
    pass

需要注意的是,这段代码没有具体实现 get_distance 函数,这个函数用来计算两个节点之间的实际距离,并应根据实际情况来实现。

三、其他说明

一)g(n)、h(n) 和 f(n)分别代表什么意思?

在 A* 算法中,g(n)、h(n) 和 f(n) 是三个重要的函数,它们共同决定了算法的搜索方向和效率。下面是每个函数的具体含义:

  1. g(n):这是从起始点到当前节点 n 的实际代价。它表示了从初始节点到达节点 n 的最短路径的成本。在图搜索中,这通常是已经走过的路径的长度或代价。

  2. h(n):这是启发式函数,用于估计从当前节点 n 到目标节点的最短路径的预期代价。这个值是一个估计值,用于指导搜索朝着目标更接近的方向前进。启发式函数的选择对 A* 算法的性能和准确性有很大影响。理想的启发式函数是保守的,即它不会高估实际的成本。

  3. f(n):这是评价函数,用于决定哪个节点将作为下一个探索的节点。它是 g(n) 和 h(n) 的和,即 f(n) = g(n) + h(n)。在 A* 算法中,总是选择具有最低 f(n) 值的节点进行扩展。这个函数的设计使得 A* 算法能够平衡两个方面:避免走过长的路径(通过 g(n))和尽快接近目标(通过 h(n))。

在实际实现中,我们通常会保持这三个值的更新,以确保搜索过程中始终选择最有可能达到目标的路径。其中,g(n) 是根据已知的实际路径长度计算的,h(n) 是基于对剩余路径的估计,而 f(n) 则是将这两者结合起来用于决策的。

二)neighbor.heuristic_cost是怎么得到的?

在 A* 算法中,neighbor.heuristic_cost(即 h(n))是每个节点到目标节点的启发式估计成本。这个值是根据启发式函数计算得出的,而启发式函数的选择取决于问题的具体情况和对问题的理解。以下是几种常见的启发式函数:

  1. 曼哈顿距离(Manhattan Distance):适用于网格布局的地图,其中只允许四个方向的直线移动(上、下、左、右)。计算方法是目标节点和当前节点在每个维度上的差的绝对值之和。公式为 |x1 - x2| + |y1 - y2|

  2. 欧几里得距离(Euclidean Distance):适用于可以任意方向移动的情况。它是节点到目标节点的直线距离,计算方法是使用两点间的距离公式 \(\sqrt{(x1 - x2)^2 + (y1 - y2)^2}\)

  3. 对角线距离:适用于可以八个方向移动的网格(上、下、左、右及四个对角线方向)。通常是曼哈顿距离和欧几里得距离的结合。

在实际实现中,你需要根据你的应用场景选择或设计合适的启发式函数。例如,在一个标准的网格布局地图上,如果你只允许上下左右移动,那么曼哈顿距离可能是一个好的选择;如果允许沿任何方向移动,则可能需要使用欧几里得距离。

启发式函数的设计对算法的效率和准确性有很大影响。理想情况下,这个函数不应该高估从当前节点到目标节点的实际成本,以保证 A* 算法能够找到最优解。在你的代码中,neighbor.heuristic_cost 应该在创建节点时根据所选的启发式函数来计算并赋值。

三)上面的代码怎么体现f(n)用于决策?

这是通过维护一个优先队列(在 Python 中使用的是 heapq)来实现的,该队列根据 f(n) 的值来排序节点。下面详细解释这个过程:

1、初始化 f(n):对于每个节点,f(n) 初始化为无穷大。对于起始节点,g(n) 设为 0(因为从起点到起点的距离为 0),并计算起始节点的 f(n)g(n) + h(n)

start_node.g = 0
start_node.f = start_node.g + start_node.heuristic_cost

2、使用优先队列:节点按照 f(n) 的值在优先队列中排序。这意味着具有最低 f(n) 值的节点将首先被探索。

heapq.heappush(open_list, start_node)

3、选择下一个节点:算法从优先队列中取出 f(n) 值最小的节点进行探索。

current_node = heapq.heappop(open_list)

在这里,heapq.heappop 会弹出具有最小 f(n) 值的节点。

4、更新 f(n):当探索节点的邻居时,如果找到了一条到达该邻居的更短路径(即更小的 g(n) 值),则更新该邻居的 g(n)f(n)

tentative_g_score = current_node.g + get_distance(current_node, neighbor)

if tentative_g_score < neighbor.g:
    neighbor.parent = current_node
    neighbor.g = tentative_g_score
    neighbor.f = neighbor.g + neighbor.heuristic_cost
    if neighbor not in open_list:
        heapq.heappush(open_list, neighbor)

在这个代码段中,f(n) 是通过更新 g(n)h(n) 来重新计算的。

通过这种方式,f(n) 值在整个算法过程中用于指导搜索的方向,优先探索那些看起来最有可能达到目标节点的路径。这是 A* 算法核心的决策机制,它平衡了路径的实际代价(通过 g(n))和对剩余代价的估计(通过 h(n)),以找到最有效的路径到达目标。

四)heapq.heappush 将节点添加到优先队列中,优先队列会根据 f(n) 的值自动排序,这是这个函数规定的吗?

heapq.heappush 函数在 Python 中用于将元素添加到堆中(堆通常用作优先队列)。在使用 heapq 模块创建的堆(优先队列)中,元素是根据其值的顺序进行排序的。默认情况下,Python 的堆是一个最小堆,这意味着具有最小值的元素总是位于堆的顶部。

在 A* 算法的上下文中,当你将节点对象添加到由 heapq 管理的优先队列时,这些节点会根据其 f(n) 值的大小进行排序。为了使 heapq 能够正确地比较节点对象并根据 f(n) 的值进行排序,你需要在节点类中定义一个比较方法。在提供的代码示例中,Node 类通过定义 __lt__ 方法来实现这一点:

class Node:
    # ...
    def __lt__(self, other):
        return self.f < other.f

这个 __lt__ 方法定义了两个节点对象之间的“小于”比较,基于它们的 f(n) 值。当 heapq.heappush 将一个节点添加到堆中时,它会使用这个方法来维护堆的顺序,确保具有最小 f(n) 值的节点总是位于堆的顶部。同样地,当使用 heapq.heappop 从堆中移除元素时,它总是移除并返回具有最小 f(n) 值的节点。这种方式确保了 A* 算法总是优先考虑似乎最有可能达到目标的节点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值