ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Python实现A*路径规划算法:从原理到代码实战

Python实现A*路径规划算法:从原理到代码实战 1. 从“寻路”到“最优”为什么我们需要A*算法如果你玩过任何一款策略游戏或者用过地图导航那你一定对“寻路”这个概念不陌生。从游戏里的小兵绕过树林攻击敌人到手机地图为你规划出避开拥堵的最快路线背后都离不开一套高效的路径规划算法。今天我们不谈那些复杂的商业系统就从一个程序员最熟悉的起点开始用Python亲手实现那个在游戏和机器人领域被封为“神技”的A*A-Star路径规划算法。你可能会问寻路不就是找一条从A点到B点的路吗用BFS广度优先搜索或者DFS深度优先搜索不行吗理论上当然可以但效率上就是天壤之别。想象一下你在一片巨大的网格地图上起点在左下角终点在右上角。BFS会像水波纹一样毫无偏见地向四面八方扩散直到碰到终点。这个过程会探索大量根本不可能通往终点的区域计算资源浪费严重。DFS则可能一头扎进一个死胡同里深挖半天同样不智能。A算法的聪明之处在于它**“有方向感”。它不仅仅看当前已经走了多远这称为g(n)从起点到当前节点n的实际代价还会预估**从当前节点到终点大概还要走多远这称为h(n)启发式函数估算的代价。A总是优先探索f(n) g(n) h(n)总和最小的节点。这个f(n)可以理解为“当前成本未来预期成本”的总报价。它引导搜索朝着终点的大致方向前进用最少的探索找到最优或接近最优的路径。这就是为什么在动态避障小车、无人机路径规划乃至法奥机械臂的MoveIt框架中A或其变种如D、LPA*用于动态重规划都是基石算法。网上能找到的A代码很多但要么过于抽象全是数学符号要么封装得太好像个黑盒子改了参数都不知道会发生什么。这篇内容我们就来逐行拆解用Python实现一个在网格地图上的A算法。我会带你理解每一行代码的意图而不仅仅是复制粘贴。我们会一起处理障碍物设计不同的启发式函数并直观地看到算法是如何一步步“思考”和“探索”的。无论你是刚学完Python语法想找项目练手还是做机器人、游戏开发遇到了真实的路径规划需求这篇内容都能给你一个清晰、可操作的起点。2. 构建舞台地图、节点与基础数据结构在写第一行算法代码之前我们必须先把“舞台”搭好。这个舞台就是我们的搜索环境。为了最直观地展示我们选择用二维网格Grid来表示地图。这在机器人学中对应栅格地图Occupancy Grid在游戏里就是Tile-Based的地图非常通用。2.1 定义地图与节点类首先我们需要一个Node类来代表网格中的每一个格子节点。每个节点需要记录哪些关键信息呢位置它在网格中的坐标(x, y)。通行状态它是空地可通行还是障碍物不可通行。算法计算值g从起点到本节点的实际代价、h到终点的预估代价、fgh总代价。父节点当前节点是由哪个节点探索过来的。这是最后回溯重建完整路径的关键。class Node: def __init__(self, x, y, walkableTrue): self.x x # 网格x坐标列 self.y y # 网格y坐标行 self.walkable walkable # 是否可通行True为空地False为障碍 self.g 0 # 从起点到当前节点的实际代价 self.h 0 # 从当前节点到终点的预估代价启发值 self.f 0 # 总代价 f g h self.parent None # 路径上的父节点用于回溯 def __eq__(self, other): # 重载‘’操作符方便比较两个节点是否为同一位置 return self.x other.x and self.y other.y def __lt__(self, other): # 重载‘’操作符用于优先队列堆比较。优先f值小的节点 return self.f other.f def __repr__(self): # 定义打印对象时的输出便于调试 return fNode({self.x}, {self.y})关键点解析__eq__方法在算法中我们需要频繁判断“当前处理的节点是不是终点”或者“这个邻居节点是不是已经在关闭列表里”。重载操作符后我们可以直接用if current_node end_node:进行判断非常直观。__lt__方法这是整个算法效率的核心之一。A*需要不断从“待探索节点集合”中取出f值最小的节点。Python的heapq模块堆队列能高效地完成这个操作但它需要知道如何比较对象的大小。我们定义Node的“小于”就是f值更小这样heapq就能自动帮我们维护一个按f排序的优先队列。walkable属性这是模拟障碍物的关键。在初始化地图时我们可以将某些节点的walkable设为False。接下来我们构建一个Grid类来管理整个地图。class Grid: def __init__(self, width, height): self.width width self.height height # 初始化一个二维列表存储Node对象 self.nodes [[Node(x, y) for x in range(width)] for y in range(height)] def get_node(self, x, y): # 安全地获取指定坐标的节点处理越界情况 if 0 x self.width and 0 y self.height: return self.nodes[y][x] # 注意列表索引是 [行][列]即 [y][x] return None def set_obstacle(self, x, y): # 设置障碍物 node self.get_node(x, y) if node: node.walkable False def get_neighbors(self, node): # 获取一个节点的所有可行走的邻居节点 neighbors [] # 定义四个方向的移动向量上、下、左、右 directions [(0, -1), (0, 1), (-1, 0), (1, 0)] for dx, dy in directions: new_x, new_y node.x dx, node.y dy neighbor self.get_node(new_x, new_y) # 如果邻居节点存在且可通行则加入列表 if neighbor and neighbor.walkable: neighbors.append(neighbor) return neighbors关键点解析nodes列表的索引这里有一个初学者极易混淆的点。我们通常用(x, y)表示坐标x是水平方向列y是垂直方向行。但在二维列表中访问元素的语法是list[row][column]即list[y][x]。在get_node方法中我们传入(x, y)但返回的是self.nodes[y][x]这是正确的映射关系。get_neighbors方法这里我们只定义了“四连通”上、下、左、右的邻居。如果你希望允许斜向移动八连通需要添加(-1, -1), (-1, 1), (1, -1), (1, 1)这四个方向并注意斜向移动的代价通常是√2约1.414倍而不是1。为了简化本篇我们先实现四连通。set_obstacle方法为我们提供了动态修改地图的能力这是模拟动态障碍物或初始化复杂地图的基础。2.2 算法核心数据结构开放列表与关闭列表A*算法维护两个核心集合开放列表Open List存放所有已发现但尚未探索的节点。它需要支持快速插入和快速取出f值最小的节点。我们使用Python的heapq模块来实现一个最小堆优先队列。关闭列表Closed List/Set存放所有已探索完毕的节点。我们使用Python的set集合来实现因为它的查找判断一个节点是否在其中速度极快O(1)。import heapq open_list [] # 将作为堆使用 closed_set set()为什么用堆Heap如果开放列表用普通列表每次寻找f最小的节点都需要遍历整个列表O(n)操作。而堆数据结构能在O(log n)的时间内完成插入和取出最小值的操作当需要处理成百上千个节点时性能差异巨大。这也是A*算法能高效运行的关键实现细节之一。3. 算法的灵魂启发式函数设计如果说g(n)代表了脚踏实地已付出的成本那么h(n)就是仰望星空对未来的预估。这个预估函数h(n)就是启发式函数。它的设计直接决定了A*算法的行为、效率和结果。启发式函数需要满足一个最重要的性质可采纳性Admissibility。即对于任意节点nh(n)必须永远不大于从n到终点的实际最短代价。如果启发函数高估了实际代价A*可能就找不到最短路径。反之低估是安全的。3.1 曼哈顿距离Manhattan Distance在我们四连通的网格世界里每次只能上下左右移动一格每步代价为1。这种情况下最常用且可采纳的启发式函数是曼哈顿距离。它得名于纽约曼哈顿街区方方正正的规划你不能斜穿大楼只能沿着街道走。def manhattan_distance(node, end_node): return abs(node.x - end_node.x) abs(node.y - end_node.y)计算方法是|x1 - x2| |y1 - y2|。它完美地匹配了我们的移动规则是实际最短路径代价的精确值在没有障碍物的情况下。因此它不仅是可采纳的甚至是“完美”的启发函数能引导A*以最高效率找到最短路径。3.2 欧几里得距离Euclidean Distance也就是我们常说的“直线距离”。计算公式为sqrt((x1-x2)^2 (y1-y2)^2)。import math def euclidean_distance(node, end_node): return math.sqrt((node.x - end_node.x)**2 (node.y - end_node.y)**2)在允许斜向移动八连通且斜向移动代价为√2时欧几里得距离是可采纳的因为直线距离永远小于等于沿网格线走的距离。但在我们仅限四连通的情况下欧几里得距离会高估实际代价因为实际必须走折线距离更长违反了可采纳性可能导致A*找不到最短路径。这是一个重要的陷阱。3.3 切比雪夫距离Chebyshev Distance定义为max(|x1-x2|, |y1-y2|)。它适用于可以朝八个方向包括斜角移动且所有方向移动代价相同通常为1的游戏场景。在这种情况下它是可采纳的。如何选择四连通网格无脑选择曼哈顿距离。八连通网格斜向代价为1选择切比雪夫距离。八连通网格斜向代价为√2可以使用对角距离Diagonal Distance它是曼哈顿距离和欧氏距离的一个折中修正公式稍复杂但能提供更准确的估计。实操心得在绝大多数网格化路径规划场景中曼哈顿距离是默认首选。它不仅计算简单快速而且完全匹配移动规则。如果你不确定就用曼哈顿距离它最安全可靠。只有在明确允许斜向移动且需要更精准的启发时才去考虑更复杂的函数。4. 逐行解剖A*算法主循环舞台搭好灵魂注入现在让我们看算法如何动起来。下面是A*算法核心函数的完整实现我们将逐段拆解。def a_star_search(grid, start, end): 在给定的Grid中从start节点寻找到end节点的路径。 返回路径Node列表如果找不到则返回空列表。 # 初始化开放列表和关闭集合 open_list [] closed_set set() # 将起点加入开放列表其g0, h由启发函数计算fgh start.g 0 start.h manhattan_distance(start, end) start.f start.g start.h heapq.heappush(open_list, start) # 使用堆操作保证open_list是最小堆 # 主循环当还有节点待探索时继续 while open_list: # 从开放列表中取出f值最小的节点堆的根节点 current_node heapq.heappop(open_list) # 如果当前节点就是终点恭喜路径找到 if current_node end: return reconstruct_path(current_node) # 回溯构建路径 # 将当前节点移入关闭集合表示已探索 closed_set.add(current_node) # 探索当前节点的所有邻居 for neighbor in grid.get_neighbors(current_node): # 如果邻居已在关闭集合中跳过 if neighbor in closed_set: continue # 计算从起点经过当前节点到达邻居节点的 tentative_g 代价 # 假设每移动一格代价为1 tentative_g current_node.g 1 # 判断邻居是否在开放列表中 in_open_list neighbor in open_list # 注意in操作对列表是O(n)对堆也是低效的。这里为了清晰先这样写后面会优化。 # 如果邻居不在开放列表或者这条新路径的g值更优 if not in_open_list or tentative_g neighbor.g: # 更新邻居节点的父节点为当前节点 neighbor.parent current_node # 更新邻居的g值 neighbor.g tentative_g # 重新计算或首次计算启发值h和总代价f neighbor.h manhattan_distance(neighbor, end) neighbor.f neighbor.g neighbor.h # 如果邻居是首次发现将其加入开放列表 if not in_open_list: heapq.heappush(open_list, neighbor) else: # 如果邻居已在开放列表中且g值被更新变小了 # 需要重新调整堆中该节点的位置因为它的f值变了。 # 一个简单粗暴但有效的方法是先移除再插入。 # 更高效的做法是使用支持减少键操作的优先队列但实现复杂。 # 这里我们采用重新入堆的方法。 # 注意由于我们重载了__lt__堆会根据新的f值自动调整。 # 但为了确保堆结构正确我们通常需要先heapify或者像下面这样操作 # 实际上因为neighbor的f值变小了它可能会上浮。 # 标准heapq不支持直接更新节点值后的重新排序一个常见技巧是 # 1. 标记节点为“无效”我们这里没有 # 2. 直接push一个新版本我们更新了原节点所以就是原节点 # 3. 在pop时判断节点是否“有效” # 为了简化我们采用“惰性”处理即使g值更新了我们也不强制重新排序堆。 # 这可能导致堆中同一个节点有多个副本不同g值状态。 # 一个简单的优化是在pop出节点后检查其g值是否与节点当前存储的g值一致即是否是最新状态。 # 我们在此处不做这个复杂优化对于教学和中小型地图够用。 pass # 暂时不处理堆的重排这是一个已知的简化可能轻微影响性能。 # 如果开放列表为空仍未找到终点说明路径不存在 return []4.1 关键行代码深度解读第14行heapq.heappush(open_list, start) 这是算法效率的起点。我们没有用open_list.append(start)然后排序而是直接用heapq.heappush。这个操作会按照我们定义的__lt__方法比较f值将start节点放入堆的合适位置时间复杂度是O(log n)。第18行current_node heapq.heappop(open_list) 这是A*的“决策点”。算法总是取出当前看来最有希望f值最小的节点进行探索。heapq.heappop会在取出后自动调整堆结构保证下一个pop出来的还是最小值复杂度也是O(log n)。第21-22行if current_node end: 为什么是而不是比较坐标因为我们重载了Node类的__eq__方法使其比较坐标。这样写代码更清晰。找到终点后调用reconstruct_path回溯。第32行if neighbor in closed_set:closed_set是set类型这个in操作的复杂度是平均O(1)非常快。它确保我们不会重复探索已经处理过的节点。第36行tentative_g current_node.g 1 计算“试探性g值”。1是移动代价Cost。在更复杂的场景中这个代价可以是地形权重如沼泽走起来慢、坡度成本等。这是A*算法能处理加权图寻找最低成本路径而非最少步数路径的关键。第39行in_open_list neighbor in open_list这里存在一个严重的性能问题open_list是一个堆本质上是列表对列表使用in操作是O(n)的线性扫描会随着开放列表变大而急剧变慢。这在算法中会被执行成千上万次成为性能瓶颈。我们必须优化它。优化方案额外维护一个与开放列表同步的set用于快速查找。open_set set() # 新增一个集合用于快速查找 # 当将节点加入堆时也加入这个集合 heapq.heappush(open_list, node) open_set.add(node) # 当从堆中取出节点时也从集合中移除 current_node heapq.heappop(open_list) open_set.remove(current_node) # 注意需要处理堆中可能存在重复节点的问题 # 判断是否在开放列表就变成了 if neighbor in open_set: # O(1)操作 ...这个优化至关重要是生产级A*实现的标配。我们将在完整代码中整合它。第42行if not in_open_list or tentative_g neighbor.g: 这是A*的“路径更新”逻辑。有两种情况需要更新邻居节点首次发现邻居不在开放列表中说明这是一条新路径。发现更优路径邻居虽然在开放列表中但我们通过当前节点到达它得到了一个更小的g值tentative_g neighbor.g。这意味着我们找到了一条到达这个邻居节点的更短路径需要更新它的信息。第54-71行的“堆重排”问题 注释里详细讨论了这个问题。当我们更新了已在堆中节点的f值后堆的内部顺序可能失效。heapq模块不提供直接更新节点优先级并重新排序的函数即“decrease-key”操作。常见的变通方法有惰性删除不直接修改堆中旧节点而是将更新后的节点再次push进堆。这样堆里可能有同一个坐标的多个节点不同状态。在pop时检查节点是否已被处理过比如通过closed_set或比较g值如果是就丢弃继续pop下一个。这种方法实现简单但堆会变大。自定义支持decrease-key的优先队列可以使用heapq但为每个节点维护一个入口entry并跟踪其当前索引实现起来较复杂。对于学习和大多数应用场景惰性删除法是足够且简单的。我们会在最终代码中采用这种方法。5. 路径回溯与可视化让结果一目了然算法找到终点后我们得到的是一个指向终点的节点它的parent指向它的来源节点以此类推最终能回溯到起点。我们需要一个函数来提取这条链。5.1 重构路径函数def reconstruct_path(node): 从终点节点开始通过parent指针回溯到起点生成路径列表。 path [] current node while current is not None: path.append((current.x, current.y)) # 存储坐标 current current.parent path.reverse() # 反转变成从起点到终点 return path这个函数很简单但却是输出结果的关键。它从终点开始沿着parent指针一路向上直到起点其parent为None然后将记录的点序反转就得到了从起点到终点的路径坐标序列。5.2 可视化用字符画打印地图和路径对于调试和演示没有什么比直观的图形更有效了。我们用简单的字符在控制台打印地图。def print_grid(grid, pathNone, startNone, endNone): 在控制台打印网格地图可叠加显示路径、起点、终点。 for y in range(grid.height): row_str for x in range(grid.width): node grid.get_node(x, y) if not node.walkable: row_str ██ # 障碍物 elif start and node start: row_str S # 起点 elif end and node end: row_str E # 终点 elif path and (x, y) in path: row_str * # 路径 else: row_str . # 空地 print(row_str)这个函数遍历所有网格根据节点的状态和是否在路径上选择不同的字符进行绘制。██表示障碍S和E是起点终点*是路径点.是空地。虽然简陋但足以清晰展示算法结果。6. 从理论到实践完整可运行的代码与测试现在我们把所有零件组装起来形成一个完整、可运行、且经过性能优化的A*搜索程序。import heapq import math class Node: def __init__(self, x, y, walkableTrue): self.x x self.y y self.walkable walkable self.g 0 self.h 0 self.f 0 self.parent None def __eq__(self, other): return self.x other.x and self.y other.y def __lt__(self, other): return self.f other.f def __hash__(self): # 要将Node对象放入set必须实现__hash__方法 return hash((self.x, self.y)) def __repr__(self): return f({self.x},{self.y}) class Grid: def __init__(self, width, height): self.width width self.height height self.nodes [[Node(x, y) for x in range(width)] for y in range(height)] def get_node(self, x, y): if 0 x self.width and 0 y self.height: return self.nodes[y][x] return None def set_obstacle(self, x, y): node self.get_node(x, y) if node: node.walkable False def get_neighbors(self, node): neighbors [] # 四方向移动 directions [(0, -1), (0, 1), (-1, 0), (1, 0)] for dx, dy in directions: new_x, new_y node.x dx, node.y dy neighbor self.get_node(new_x, new_y) if neighbor and neighbor.walkable: neighbors.append(neighbor) return neighbors def manhattan_distance(node, end_node): return abs(node.x - end_node.x) abs(node.y - end_node.y) def a_star_search(grid, start, end): open_list [] closed_set set() open_set set() # 用于快速查找的集合 start.g 0 start.h manhattan_distance(start, end) start.f start.g start.h heapq.heappush(open_list, start) open_set.add(start) while open_list: # 使用惰性删除法pop出的节点可能不是最新的g值可能已被更新 # 我们需要检查它是否仍在open_set中或者其g值是否是最新的。 # 一个更简单的方法是检查它是否在closed_set中如果在就跳过。 current_node heapq.heappop(open_list) # 如果当前节点不在open_set中说明它是旧状态的“无效”节点跳过 if current_node not in open_set: continue open_set.remove(current_node) if current_node end: return reconstruct_path(current_node) closed_set.add(current_node) for neighbor in grid.get_neighbors(current_node): if neighbor in closed_set: continue tentative_g current_node.g 1 if neighbor not in open_set: # 首次发现 neighbor.parent current_node neighbor.g tentative_g neighbor.h manhattan_distance(neighbor, end) neighbor.f neighbor.g neighbor.h heapq.heappush(open_list, neighbor) open_set.add(neighbor) elif tentative_g neighbor.g: # 找到更优路径更新邻居 neighbor.parent current_node neighbor.g tentative_g neighbor.h manhattan_distance(neighbor, end) neighbor.f neighbor.g neighbor.h # 由于heapq不支持decrease-key我们将更新后的节点再次push # 旧状态的节点将在pop时被上面的检查跳过 heapq.heappush(open_list, neighbor) # open_set中已经存在neighbor所以不用重复add return [] def reconstruct_path(node): path [] current node while current is not None: path.append((current.x, current.y)) current current.parent path.reverse() return path def print_grid(grid, pathNone, startNone, endNone): for y in range(grid.height): row_str for x in range(grid.width): node grid.get_node(x, y) if not node.walkable: row_str ██ elif start and node start: row_str S elif end and node end: row_str E elif path and (x, y) in path: row_str * else: row_str . print(row_str) # 测试代码 if __name__ __main__: # 1. 创建一个10x10的地图 grid Grid(10, 10) # 2. 设置一些障碍物形成一道墙 for y in range(3, 8): grid.set_obstacle(5, y) # 设置一个单独障碍 grid.set_obstacle(2, 2) # 3. 定义起点和终点 start_node grid.get_node(1, 1) end_node grid.get_node(8, 8) print(初始地图) print_grid(grid, startstart_node, endend_node) print(\n *30 \n) # 4. 执行A*搜索 path a_star_search(grid, start_node, end_node) # 5. 打印结果 if path: print(f找到路径路径点坐标{path}) print(\n带路径的地图) print_grid(grid, pathpath, startstart_node, endend_node) else: print(未找到路径)运行这段代码你将在控制台看到类似下面的输出初始地图 . . . . . . . . . . . S . . . . . . . . . . ██ . . . . . . . . . . . . ██ . . . . . . . . . ██ . . . . . . . . . ██ . . . . . . . . . ██ . . . . . . . . . ██ . . . . . . . . . . . . E . . . . . . . . . . . 找到路径路径点坐标[(1, 1), (1, 2), (2, 2)是障碍物, (1, 3), (1, 4), (2, 5), (3, 6), (4, 7), (5, 8), (6, 8), (7, 8), (8, 8)] 注意路径会绕开障碍物墙和(2,2)的点你会看到算法成功地绕过了垂直的障碍墙找到了从左上角到右下角的路径。通过修改障碍物、起点和终点的位置你可以测试各种复杂场景。7. 性能优化与常见问题排查一个基础的A*实现已经完成但在实际项目中我们还需要考虑更多。7.1 开放列表查找的优化已实现如前所述我们使用open_set一个set来配合open_list堆将“判断节点是否在开放列表”的操作从O(n)降为O(1)。这是最重要的优化之一。7.2 启发式函数权重权衡速度与最优性有时我们可能不需要绝对最短路径而是希望搜索更快。这时可以给启发式函数h(n)加一个权重ww 1即f(n) g(n) w * h(n)。这会让算法更“贪婪”地朝向终点前进大大减少探索的节点数从而加快搜索速度但找到的路径可能不是最短的。这在游戏AI中非常常见被称为加权A*。# 在计算f值时 weight 1.5 # 权重因子1 加快搜索但可能牺牲最优性1 是标准A* neighbor.f neighbor.g weight * neighbor.h7.3 处理“无路径”的情况我们的代码中当while循环结束open_list为空时返回空列表[]表示无路径。在实际应用中你需要根据这个结果进行后续处理比如让单位等待、尝试其他目标点或向用户报告错误。7.4 地图表示与代价系统我们使用了简单的网格和固定移动代价1。真实场景可能更复杂非网格地图A*同样适用于图Graph。你只需要将Node类改为图的顶点get_neighbors方法返回该顶点的所有邻接顶点并将移动代价1替换为边的权重即可。地形代价可以在Grid的每个Node中增加一个cost字段表示通过该格子的基础代价。在计算tentative_g时使用current_node.g neighbor.cost。动态障碍物对于动态避障小车或游戏中的移动单位你需要实现重规划。一种方法是定期或当检测到新障碍时以当前位置为起点重新运行A*。对于频繁变化的场景可以考虑D* Lite等更高效的增量重规划算法。7.5 调试与可视化进阶控制台字符画对于简单演示够用但对于复杂路径或大型地图就不够直观了。你可以考虑使用matplotlib或pygame库进行图形化绘制。这能让你更清晰地观察算法的探索过程例如用不同颜色标记开放列表、关闭列表中的节点对于理解和调试算法有巨大帮助。一个典型的调试问题算法陷入死循环或找不到明显存在的路径。排查步骤检查启发式函数的可采纳性确保h(n)没有高估。在我们的四连通网格中使用欧几里得距离就会导致此问题。检查障碍物设置确认起点和终点本身不是障碍物。检查邻居获取函数get_neighbors是否正确过滤了不可行走的节点和边界检查代价计算tentative_g的计算是否正确如果代价出现负数或零会导致算法行为异常。可视化探索过程在搜索循环中每探索一定数量的节点后打印一次地图标记出closed_set和open_list中的节点看看算法卡在哪里了。8. 举一反三从网格A*到更广阔的应用通过这个网格化的A*实现你已经掌握了算法的核心思想。它可以作为跳板扩展到无数应用场景游戏开发这是A最经典的应用领域。无论是2D像素游戏还是3D世界中的NPC导航通常需要先将3D环境转换为导航网格NavMesh再在网格上使用A。机器人路径规划移动机器人如扫地机器人、仓库AGV在栅格地图上的静态路径规划。结合传感器数据实时更新地图中的障碍物信息即可实现基本的动态避障。网络路由数据包在网络中选择路径路由器之间的跳数或延迟可以作为代价IP地址作为节点。拼图游戏求解如八数码、华容道等可以将每一种棋盘状态看作一个节点一次合法移动看作一条边用A*搜索最优解。要实现这些你需要根据具体问题重新定义“节点”、“邻居”和“代价”。例如在八数码问题中节点是一个3x3的棋盘状态邻居是通过一次滑动可以得到的所有新状态代价通常是移动步数g加上预估离目标状态的差异h例如错位棋子的数量。最后一点个人体会A*算法之美在于它在“最优性”和“效率”之间取得了精妙的平衡。理解它最好的方式就是像我们今天这样亲手实现一遍并尝试修改参数如启发函数权重、增加地形代价、甚至将其改造成Dijkstra算法设h(n)0或贪婪最佳优先搜索设g(n)0。通过对比它们的行为差异你会对图搜索算法有更深刻的认识。这个小小的网格寻路程序是你进入路径规划、人工智能搜索算法大门的一块坚实基石。
返回列表