启发函数 (Heuristic Function) —Octile

启发函数 (Heuristic Function)

盲目搜索会浪费很多时间和空间, 所以我们在路径搜索时, 会首先选择最有希望的节点, 这种搜索称之为 "启发式搜索 (Heuristic Search)"

如何来界定"最有希望"? 我们需要通过 启发函数 (Heuristic Function) 计算得到.

对于网格地图来说, 如果只能四方向(上下左右)移动, 曼哈顿距离(Manhattan distance) 是最合适的启发函数.

function Manhattan(node) =
    dx = abs(node.x - goal.x)
    dy = abs(node.y - goal.y)
    // 在最简单的情况下, D 可以取 1, 返回值即 dx + dy
    return D * (dx + dy)

如果网格地图可以八方向(包括斜对角)移动, 使用 切比雪夫距离(Chebyshev distance) 作为启发函数比较合适.

function Chebyshev(node) =
    dx = abs(node.x - goal.x)
    dy = abs(node.y - goal.y)
    // max(dx, dy) 保证了斜对角的距离计算
    return D * max(dx, dy)

如果地图中允许任意方向移动, 不太建议使用网格 (Grid) 来描述地图, 可以考虑使用 路点 (Way Points) 或者 导航网格 (Navigation Meshes) , 此时使用 欧式距离(Euclidean distance) 来作为启发函数比较合适.

function heuristic(node) =
    dx = abs(node.x - goal.x)
    dy = abs(node.y - goal.y)
    // 在最简单的情况下, D 可以取 1, 返回值即 sqrt(dx * dx + dy * dy)
    return D * sqrt(dx * dx + dy * dy)

欧式距离因为有一个 sqrt() 运算, 计算开销会增大, 所以可以使用 Octile 距离 来优化(不知道怎么翻译), Octile 的核心思想就是假定只能做 45 度角转弯.

function heuristic(node) =
    dx = abs(node.x - goal.x)
    dy = abs(node.y - goal.y)
    k = sqrt(2) - 1
    return max(dx, dy) + k * min(dx, dy)

下图是一个启发函数的简单示意

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETkBqdWJvYm9sdjM2OQ,size_16,color_FFFFFF,t_70,g_se,x_16

 

 

Dijkstra

Dijkstra 算法 是由计算机大牛 Dijkstra 在1956年提出来的, 它是一个"精确"的寻路算法, 能保证发现节点之间的最短路径, 本质是一个 广度优先搜索 .

  • g(n): 从起始点到当前点 n 的开销, 在网格地图中一般就是步长.
  • 将起始点 S 加入 open-list.
  • 从当前的 open-list 中选取 g(n) 最优(最小)的节点 n, 加入 closed-list
  • 遍历 n 的后继节点 ns
  • 如果 ns 是新节点, 加入 open-list
  • 如果 ns 已经在 open-list 中, 并且当前 h(ns) 更优, 则更新 ns 并修改 parent
  • 迭代, 直到找打目标节点 D, 回溯得到路径

Dijkstra 算法的效率并不高, 时间复杂度为 O(n^2), 但是它保证了寻路结果是最短距离.

Best-First-Search

Best-first Search 最佳优先搜索, 简称为 BFS.

BFS 根据启发式函数的推断, 每次迭代最优的节点, 直到寻找到目标节点. 一个简单的算法流程如下:

  • h(n) 代表从当前点 n 到目标点 S 的估算开销, 即启发函数.
  • 将起始点 S 加入 open-list.
  • 从当前的 open-list 中选取 h(n) 最优(最小)的节点 n, 加入 closed-list
  • 遍历 n 的后继节点 ns
  • 如果 ns 是新节点, 加入 open-list
  • 如果 ns 已经在 open-list 中, 并且当前 h(ns) 更优, 则更新 ns 并修改 parent
  • 迭代, 直到找打目标节点 D, 回溯得到路径

如果不使用 open-list, 直接每次都寻找 n 的后继节点中最优的节点, BFS 则退化为贪心最佳优先搜索 (Greedy BFS).

不管是 BFS 还是 Greedy-BFS, 本质上都还是寻找 局部最优 , 虽然效率会比 Dijkstra 高很多, 但最终得到的解并不一定是全局最优(最短距离), 在地图中有障碍物时尤为明显.

A-Star

A-Star 俗称 A* , 是应用最广的寻路算法, 在很多场景下都适用. A* 兼顾了 Dijkstra 的准确度和 BFS 的效率.

  • f(n) = g(n) + h(n) , g(n) 和 h(n) 的定义同上
  • 当 g(n) 退化为 0, 只计算 h(n) 时, A* 即退化为 BFS.
  • 当 h(n) 退化为 0, 只计算 g(n) 时, A* 即退化为 Dijkstra 算法.
  • 将起始点 S 加入 open-list.
  • 从当前的 open-list 中选取 f(n) 最优(最小)的节点 n, 加入 close-list
  • 遍历 n 的后继节点 ns
  • 如果 ns 是新节点, 加入 open-list
  • 如果 ns 已经在 open-list 中, 并且当前 f(ns) 更优, 则更新 ns 并修改 parent
  • 迭代, 直到找打目标节点 D.

A* 算法在从起始点到目标点的过程中, 尝试平衡 g(n) 和 h(n), 目的是找到最小(最优)的 f(n) .

A-Star 算法衍生

如果在每一步迭代扩展中, 都去除掉质量比较差的节点, 即选取 有限数量的后继节点 , 这样减少了空间开销, 并提高了时间效率. 但是缺点可能会丢弃潜在的最佳方案. 这就是 集束搜索(Beam Search) , 本质上还是局部最优的贪心算法.

当距离起点越近, 快速前行更重要; 当距离目标点越近, 到达目标更重要. 基于这个原则, 可以引入权重因子, A* 可以演进为选取 f(n) = g(n) + w(n) * h(n) 最优的点, w(n) 表示在点 n 的权重. w(n) 随着当距离目标点越近而减小. 这是 动态加权 A* (Dynamic Weighting A*) .

当从出发点和目标点同时开始做双向搜索, 可以利用并行计算能力, 更快的获得计算结果, 这是 双向搜索 (Bidirectional Search) . 此时, 向前搜索 f(n) = g(start, n) + h(n, goal), 向后搜索 f(m) = g(m, goal) + h(start, m), 所以双向搜索最终可以归结为选取 g(start, n) + h(m, n) + g(m, goal) 最优的一对点(m, n).

A* 在静态地图中表现很棒, 但是在动态环境中(例如随机的障碍物), 却不太合适, 一个基于 A* 算法的 D* 算法(D-Star, Dynamic A*) 能很好的适应这样的动态环境. D* 算法中, 没有 g(n), f(n) 退化为 h(n), 但是每一步 h(n) 的计算有所不同: h(n) = h(n-1) + h(n-1, n) , c(n-1, n) 表示节点 n-1 到节点 n 的开销.

### A*算法中的启发函数概念与实现 #### 启发函数的定义与作用 A*算法是一种基于启发式的最佳优先搜索算法,其核心在于通过评价函数 \( f(n) \) 来评估当前节点的状态并决定下一步的选择。评价函数由两部分组成: \[ f(n) = g(n) + h(n) \] 其中: - \( g(n) \) 表示从起始节点到当前节点的实际代价[^1]。 - \( h(n) \)启发函数,表示从当前节点到目标节点的最佳路径的估计代价。 \( h(n) \) 的设计直接影响算法的行为和效率。它提供了关于如何接近目标的信息,从而指导搜索的方向[^2]。 --- #### 启发函数的设计原则 为了使 A* 算法能够找到最优解,启发函数需满足 **可接受性条件** 和 **一致性条件**: 1. **可接受性条件**: \( h(n) \leq d(n, goal) \),即启发函数的估值不应超过真实距离。这可以保证最终得到的是全局最优解。 2. **一致性条件**: 对于任意两个相邻节点 \( n_1 \) 和 \( n_2 \),应有: \[ h(n_1) \leq c(n_1, n_2) + h(n_2) \] 其中 \( c(n_1, n_2) \) 是从 \( n_1 \) 到 \( n_2 \) 的实际移动成本。这一性质进一步提高了搜索效率[^3]。 当 \( h(n) \equiv 0 \) 时,A*退化为 Dijkstra 算法;而当仅依赖 \( h(n) \) 时,A*则表现为贪婪算法。 --- #### 经典启发函数实例 以下是几种常见的启发函数形式及其适用场景: 1. **欧几里得距离**(适用于连续平面环境) \[ h(n) = \sqrt{(x_{goal} - x_n)^2 + (y_{goal} - y_n)^2} \] 此方法适合无阻碍或障碍较少的地图,能快速估算直线距离[^4]。 2. **曼哈顿距离**(适用于网格地图) \[ h(n) = |x_{goal} - x_n| + |y_{goal} - y_n| \] 曼哈顿距离假设运动只能沿水平或垂直方向进行,在棋盘类问题中有广泛应用。 3. **切比雪夫距离**(支持斜向移动) \[ h(n) = \max(|x_{goal} - x_n|, |y_{goal} - y_n|) \] 当允许对角线移动时,此公式更为合适。 4. **混合启发函数** 结合多种距离计算方式以适应复杂地形需求。例如: ```python dx = abs(x_goal - x_current) dy = abs(y_goal - y_current) h = max(dx, dy) + (1.41 - 1) * min(dx, dy) ``` --- #### 启发函数的影响分析 不同的启发函数会对算法性能产生显著差异: - 若 \( h(n) \) 较小,则更倾向于广度优先搜索模式,扩展更多节点但确保准确性; - 若 \( h(n) \) 接近真实值,则大幅减少不必要的探索范围,提升速度; - 若 \( h(n) \) 大于实际距离,则可能丢失最优解。 因此,合理选择启发函数对于平衡时间开销与解决方案质量至关重要。 --- ```python def heuristic(node, goal): """ 计算启发函数 """ # 使用曼哈顿距离作为简单例子 return abs(goal[0] - node[0]) + abs(goal[1] - node[1]) class Node: def __init__(self, position, parent=None): self.position = position self.parent = parent self.g = 0 # 已知代价 self.h = 0 # 启发函数值 @property def f(self): return self.g + self.h # 初始化开放列表、关闭列表以及起点终点 start_node = Node(start_position) end_node = Node(end_position) open_list = [] closed_list = [] # 添加起点至开放列表 open_list.append(start_node) while open_list: current_node = min(open_list, key=lambda o: o.f) if current_node.position == end_node.position: path = [] # 构建路径 while current_node is not None: path.append(current_node.position) current_node = current_node.parent return path[::-1] open_list.remove(current_node) closed_list.append(current_node) neighbors = get_neighbors(current_node.position) # 获取邻居节点 for neighbor in neighbors: if neighbor in closed_list: continue tentative_g = current_node.g + cost_to_move(neighbor) if neighbor not in open_list or tentative_g < neighbor.g: neighbor.g = tentative_g neighbor.h = heuristic(neighbor.position, end_node.position) neighbor.parent = current_node if neighbor not in open_list: open_list.append(neighbor) ``` ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

jubobolv369

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

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

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

打赏作者

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

抵扣说明:

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

余额充值