使用 Python 实现网格地图 A* 路径规划教程

使用 Python 实现网格地图 A* 路径规划教程

本教程详细介绍了如何在 python 中实现网格地图的路径规划。利用类似广度优先搜索的策略,从起点开始,逐步将可通行节点标记为指向起点的方向。一旦到达目标点,即可通过回溯这些方向,高效地重建出从起点到目标的最优路径。文章包含示例代码,帮助读者理解并应用此寻路方法。

1. 简介与问题定义

路径规划是人工智能计算机科学中的一个基本问题,广泛应用于游戏开发、机器人导航和物流优化等领域。本教程将重点介绍如何在二维网格地图中寻找从起点到终点的最短路径。

我们的地图表示为一个嵌套列表(即二维数组),其中包含以下几种值:

0: 代表墙壁或不可通行区域。1: 代表空地或可通行区域。2, 3, 4: 代表不同类型的障碍物,但同样不可通行。*: 代表路径的起点。$$$: 代表路径的终点。

目标是编写一个 Python 函数,能够:

从起点开始,在地图中填充表示回溯方向的符号,直到到达终点。从终点开始,沿着这些方向符号回溯,以重建出最短路径。返回包含该路径的地图(路径上的节点被特殊符号标记)。

需要注意的是,尽管问题提及 A* 算法,但针对无权图(即所有可通行路径的成本相同,例如本例中每一步移动成本都视为1)寻找最短路径时,广度优先搜索(BFS)是一种简单且有效的算法,它能够保证找到最短路径。本教程将基于 BFS 的思想实现路径填充和回溯。

立即学习“Python免费学习笔记(深入)”;

2. 地图表示与基本设置

首先,我们定义一个示例地图和一些常量,包括起点、终点以及用于标记方向的符号。

mapList = [ # 示例地图,你可以根据需要修改    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],    [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],    [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],    [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 2, 2, 1, 1, 2, 2, 0, 0],    [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 2, 2, 1, 1, 2, 2, 0, 0],    [0, 0, 1, 1, 2, 2, 3, 3, 1, 1, 3, 3, 1, 1, 1, 1, 1, 1, 0, 0],    [0, 0, 1, 1, 2, 2, 3, 3, 1, 1, 3, 3, 1, 1, 1, 1, 1, 1, 0, 0],    [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 0, 0],    [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 0, 0],    [0, 0, 2, 2, 2, 2, 1, 1, 2, 2, 3, 3, 1, 1, 1, 1, 1, 1, 0, 0],    [0, 0, 2, 2, 2, 2, 1, 1, 2, 2, 3, 3, 1, 1, 1, 1, 1, 1, 0, 0],    [0, 0, 1, 1, 2, 2, 1, 1, 3, 3, 1, 1, 1, 1, 1, 1, 2, 2, 0, 0],    [0, 0, 1, 1, 2, 2, 1, 1, 3, 3, 1, 1, 1, 1, 1, 1, 2, 2, 0, 0],    [0, 0, 1, 1, 2, 2, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0],    [0, 0, 1, 1, 2, 2, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0],    [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],    [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],    [0, 0, 1, 1, 3, 3, 3, 3, 1, 1, 1, 1, 3, 3, 1, 1, 1, 1, 0, 0],    [0, 0, 1, 1, 3, 3, 3, 3, 1, 1, 1, 1, 3, 3, 1, 1, 1, 1, 0, 0],    [0, 0, 1, 1, 3, 3, 3, 3, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 0, 0],    [0, 0, 1, 1, 3, 3, 3, 3, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 0, 0],    [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 0, 0],    [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 0, 0],    [0, 0, 1, 1, 3, 3, 3, 3, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 0, 0],    [0, 0, 1, 1, 3, 3, 3, 3, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 0, 0],    [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 0, 0],    [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 0, 0],    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],]# 定义起点和终点坐标及符号sourceRow, sourceColumn = 6, 2destinationRow, destinationColumn = 2, 15sourceSymbol = "*"destinationSymbol = "$$$"# 定义方向符号up = "^"down = "v"right = ">"left = "<"# 在地图上标记起点和终点mapList[sourceRow][sourceColumn] = sourceSymbolmapList[destinationRow][destinationColumn] = destinationSymboldef print_map(map_data):    """辅助函数:打印地图"""    for row in map_data:        print(" ".join(map(str, row)))    print("-" * 40)print("--- 初始地图 ---")print_map(mapList)

3. 路径填充:广度优先搜索 (BFS) 策略

这一步的目标是从起点开始,向所有可通行的相邻节点扩散,并用方向符号标记这些节点。每个方向符号都指向其父节点(即扩散过程中上一步到达的节点),这样最终我们可以从终点沿着这些符号回溯到起点。

我们使用一个队列 tempList 来实现 BFS。每次从队列中取出一个点,检查其四个相邻点:

如果相邻点是终点,则停止扩散。如果相邻点是可通行区域 (1),则将其标记为指向当前点的方向,并加入队列。

# 用于 BFS 队列tempList = [(sourceRow, sourceColumn)]def fill_with_directions(current_point: tuple, current_map: list):    """    从当前点开始,向四周可通行区域填充方向符号。    方向符号指示从该点回溯到父节点(即当前点)的方向。    """    r, c = current_point    # 定义四个方向的偏移量:(dr, dc)    # (1, 0) -> 下, (-1, 0) -> 上, (0, 1) -> 右, (0, -1) -> 左    neighbor_offsets = [(-1, 0), (1, 0), (0, -1), (0, 1)] # 上,下,左,右    for dr, dc in neighbor_offsets:        nr, nc = r + dr, c + dc # 相邻点的坐标        # 检查边界        if not (0 <= nr < len(current_map) and 0 <= nc < len(current_map[0])):            continue        # 如果到达终点        if current_map[nr][nc] == destinationSymbol:            return True # 找到终点,返回True        # 如果相邻点是可通行区域 (1)        if current_map[nr][nc] == 1:            # 根据相对位置填充方向符号            if dr == -1: # 相邻点在上方,当前点在下方,所以相邻点应指向下                current_map[nr][nc] = down            elif dr == 1: # 相邻点在下方,当前点在上方,所以相邻点应指向上                current_map[nr][nc] = up            elif dc == -1: # 相邻点在左方,当前点在右方,所以相邻点应指向右                current_map[nr][nc] = right            elif dc == 1: # 相邻点在右方,当前点在左方,所以相邻点应指向左                current_map[nr][nc] = left            # 将新填充的点加入队列,以便后续探索            tempList.append((nr, nc))    return False # 未找到终点# 执行 BFS 扩散while tempList:    current_point = tempList.pop(0) # 取出队列中的第一个点    if fill_with_directions(current_point, mapList):        print("--- 填充方向后的地图 (到达终点) ---")        print_map(mapList)        break # 找到终点,停止扩散

4. 路径回溯与重建

当 fill_with_directions 函数返回 True 时,表示我们已经从起点扩散到了终点。此时,地图上除了起点和终点,所有路径上的可通行节点都被标记了方向符号。接下来,我们将从终点开始,沿着这些方向符号反向追踪,直到回到起点,并将路径上的节点标记为 *。

# 从终点开始回溯current_path_point = None# 首先找到终点周围哪个点被标记了方向,作为回溯的起点# 定义四个方向的偏移量neighbor_offsets = [(-1, 0), (1, 0), (0, -1), (0, 1)] # 上,下,左,右possible_signs = [up, down, left, right]for dr, dc in neighbor_offsets:    nr, nc = destinationRow + dr, destinationColumn + dc    # 检查边界    if not (0 <= nr < len(mapList) and 0 <= nc < len(mapList[0])):        continue    if mapList[nr][nc] in possible_signs:        current_path_point = (nr, nc)        break# 回溯并标记路径while current_path_point:    r, c = current_path_point    current_item = mapList[r][c]    # 如果回溯到起点,则停止    if current_item == sourceSymbol:        current_path_point = None        break    # 将当前点标记为路径的一部分    mapList[r][c] = '*'    # 根据当前点的方向符号,移动到下一个回溯点(即其父节点)    if current_item == up: # 当前点指向父节点上方,说明父节点在当前点下方        current_path_point = (r + 1, c)    elif current_item == down: # 当前点指向父节点下方,说明父节点在当前点上方        current_path_point = (r - 1, c)    elif current_item == left: # 当前点指向父节点左方,说明父节点在当前点右方        current_path_point = (r, c + 1)    elif current_item == right: # 当前点指向父节点右方,说明父节点在当前点左方        current_path_point = (r, c - 1)    else:        # 理论上不应该发生,除非路径断裂或遇到非方向符号的可通行区域        print(f"Error: Encountered unexpected item {current_item} at {current_path_point}")        breakprint("--- 最终路径地图 ---")print_map(mapList)

5. 完整函数封装

为了方便使用,我们可以将上述逻辑封装到一个函数中。

def find_path_in_map(initial_map: list, start_coords: tuple, end_coords: tuple):    """    在网格地图中寻找从起点到终点的最短路径。    Args:        initial_map (list): 嵌套列表表示的地图。                            0: 墙壁, 1: 可通行, 2/3/4: 障碍物。        start_coords (tuple): 起点坐标 (row, column)。        end_coords (tuple): 终点坐标 (row, column)。    Returns:        list: 包含标记路径的地图,如果找不到路径则返回None。    """    # 创建地图的深拷贝,避免修改原始地图    current_map = [row[:] for row in initial_map]    sourceRow, sourceColumn = start_coords    destinationRow, destinationColumn = end

以上就是使用 Python 实现网格地图 A* 路径规划教程的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1378339.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月14日 19:40:54
下一篇 2025年12月14日 19:41:09

相关推荐

  • 如何用Go或Python获取手机通话记录?

    访问手机通话记录:技术途径与权限限制 想用Go或Python程序读取手机通话记录?这并非直接通过这些语言就能实现。Go和Python本身无法直接访问设备的底层数据。要实现这一目标,必须借助系统原生语言(如Android的Java/Kotlin或iOS的Swift/Objective-C)提供的API…

    2025年12月15日
    000
  • 如何用Go和Python获取电话号码通话记录?

    Go与Python获取电话号码通话记录:方法与挑战 获取特定电话号码的通话记录,在某些情况下是必要的,但同时也是一项复杂且敏感的任务。本文将探讨使用Go和Python实现这一目标的可行性及方法。 Go语言 目前,Go语言生态系统中缺乏直接获取通话记录的原生支持。Android系统本身对通话记录的访问…

    2025年12月15日
    000
  • 如何用Go或Python检索特定号码的通话记录?

    使用Go或Python提取特定号码的通话记录 本文探讨如何利用Go或Python编程语言从设备或服务中提取特定电话号码的通话记录。 挑战: 如何通过Go或Python代码访问并提取设备或服务中特定号码的通话记录? 立即学习“Python免费学习笔记(深入)”; 解决方案: 设备层面: 直接从设备层面…

    2025年12月15日
    000
  • 消息队列如何高效地撤回已排队消息?

    提升消息队列消息撤回效率 在消息队列系统中,需要撤回已排队消息的情况并不少见。传统的数据库查询方法效率低下,本文将介绍两种优化方案,有效减少数据库交互,提升系统性能。 优化方案 为了避免频繁的数据库查询,我们可以采用以下两种策略: 利用辅助数据结构: 使用一个内存映射(map)存储待撤回消息的ID。…

    2025年12月15日
    000
  • RocketMQ消息撤回:如何高效替代数据库查询?

    RocketMQ高效消息撤回机制 在消息队列中,撤回待发送消息是常见需求。本文针对阿里云RocketMQ,探讨高效的消息撤回方案,以替代低效的数据库查询方法。 挑战: 传统方案依赖数据库查询判断消息发送状态,效率低下。如何优化? 解决方案: 方案一:内存映射表 使用内存映射表(例如HashMap)存…

    2025年12月15日
    000
  • RocketMQ消息撤回:如何高效避免发送特定消息?

    RocketMQ高效消息拦截策略 在RocketMQ等消息队列中,拦截特定消息至关重要。本文提供一种高效的方案,避免发送指定消息,提升系统性能。 方案核心:利用内存数据结构或缓存机制快速判断消息是否需要发送,减少数据库查询。 1. 临时MAP方案 (适用于少量消息) 对于数量较少的不可发送消息,可以…

    2025年12月15日
    000
  • Go语言NSQ任务执行超时导致IO错误:如何避免“write tcp write: broken pipe”?

    Go语言NSQ任务超时引发的IO错误及解决方案 在使用Go语言开发基于NSQ的异步任务处理程序时,如果任务执行时间过长,可能会导致客户端出现IO错误,报错信息通常为“write tcp write: broken pipe”。这是因为NSQ服务器在客户端处理任务超过规定时间(默认2分钟)后,会主动断…

    2025年12月15日
    000
  • Go语言中如何为特定用户实现细粒度锁?

    Go语言细粒度用户锁实现 Go语言的sync.Mutex常用于锁定共享资源,但有时需要更精细的控制,例如针对特定用户进行锁定。本文将探讨如何在Go中实现针对特定用户的细粒度锁。 方案选择:取决于应用场景 锁的实现策略取决于应用是单进程还是分布式: 立即学习“go语言免费学习笔记(深入)”; 分布式应…

    2025年12月15日
    000
  • Go服务与Nginx跨域:为什么POST请求失败而GET请求正常?

    Go服务与Nginx跨域配置:解决POST请求失败问题 在使用Go构建后端服务并结合Nginx进行跨域处理时,经常遇到GET请求成功,而POST请求失败的情况。这通常是因为POST请求在发送数据前会先发送一个OPTIONS预检请求。 因此,需要在Nginx配置中正确处理OPTIONS请求。 以下是解…

    2025年12月15日
    000
  • Nginx跨域配置:为什么我的POST请求被阻止而GET请求正常?

    Nginx跨域配置问题:POST请求被拦截 本文分析Nginx跨域配置中GET请求正常,但POST请求被阻止的原因,并提供解决方案。 现有Nginx配置仅允许GET请求跨域访问。要解决POST请求跨域问题,需添加如下配置: add_header access-control-allow-method…

    2025年12月15日
    000
  • 高并发下如何保证领券系统的优惠券数据一致性?

    Go 语言高并发领券系统的数据一致性方案 在高并发领券系统中,确保优惠券数据的一致性至关重要。本文将探讨如何利用 Go 语言的并发控制机制解决这一挑战。 领券流程 用户领取优惠券的流程如下: 查询优惠券信息,验证优惠券状态、剩余数量以及用户是否已领取。在缓存(例如 Redis)中将可用数量减 1。检…

    2025年12月15日
    000
  • Go语言加锁:如何高效处理并发访问和分布式环境下的锁问题?

    Go 语言高效锁机制详解 Go 语言的 sync.Mutex 是处理并发访问的常用工具。然而,在某些场景下,例如需要针对特定用户或对象加锁,sync.Mutex 可能并非最佳选择。 基于 UID 的锁实现 针对需要对特定用户或对象进行加锁的情况,我们可以使用基于 UID 的锁机制。此方法需要创建一个…

    2025年12月15日
    000
  • Go语言中如何实现针对特定实体的自定义锁?

    Go语言自定义锁:针对特定实体的锁机制 问题:Go语言的sync.Mutex锁适用于全局资源保护,但如何实现针对特定实体(例如用户ID)的自定义锁? 解决方案: 针对不同场景,解决方案有所不同: 立即学习“go语言免费学习笔记(深入)”; 分布式环境: 使用分布式锁服务,例如Redis或Etcd。这…

    2025年12月15日
    000
  • Go语言中如何实现特定用户加锁?

    Go语言中如何实现特定用户加锁? Go语言的sync.Mutex通常用于保护共享资源,但无法直接用于特定用户加锁。本文介绍两种解决方案: 方案一:基于UID的全局锁 此方案创建一个全局的UID锁映射,使用UID作为键获取对应用户的锁。 立即学习“go语言免费学习笔记(深入)”; 实现: import…

    2025年12月15日
    000
  • 高并发抢券场景下,Go语言如何高效安全地进行加锁?

    Go语言在高并发抢券场景下的加锁策略 在高并发抢购优惠券的场景中,合理的加锁机制至关重要。本文针对此场景,提出一种高效安全的加锁策略。 场景概述 系统需要处理大量的抢券请求,并进行以下操作: 立即学习“go语言免费学习笔记(深入)”; 检查优惠券状态、剩余数量以及用户是否已领取。更新缓存中的可领取数…

    2025年12月15日
    000
  • Go语言中,数据库操作的协程能否共享同一个连接?

    Go语言协程与数据库连接共享 Go语言的协程(goroutine)以其轻量级并发执行能力而闻名。在数据库操作中,一个关键问题是:多个协程是否可以共享同一个数据库连接? 答案是肯定的。 Go语言的数据库驱动程序(例如database/sql包以及其他如mongo-driver或redis-go)通常是…

    2025年12月15日
    000
  • Go语言如何实现基于用户ID的条件加锁?

    Go语言条件加锁实现:基于用户ID Go语言的sync.Mutex锁适用于保护共享资源,但有时需要根据特定条件(如用户ID)进行加锁。本文介绍如何使用sync.Map实现基于用户ID的条件加锁。 基于用户ID的加锁机制 此方法的核心是使用一个全局的sync.Map来存储用户ID和对应锁的映射关系。 …

    2025年12月15日
    000
  • Beego框架下,如何高效地从Redis缓存中取出JSON对象?

    beego cache redis json 取出 json 对象的两种方法 想要从 redis 中取出以 json 字符串形式存储的数据,有两种常用的方法: 方法一:直接使用 cache.getstring() cachestr := cache_con.getstring(“area”)fmt.…

    好文分享 2025年12月15日
    000
  • 如何打造一个专属的文本编辑器?

    创建你专属的文本编辑器 许多开发者都梦想拥有一个完全符合自己需求的文本编辑器。本文将为想要深入了解这一过程的开发者提供一些实用建议。 用户界面选择 选择合适的GUI框架至关重要,QT是一个非常不错的选择。它支持跨平台,拥有丰富的控件和布局选项,并且性能优越。 跨平台兼容性 跨平台兼容性取决于你选择的…

    2025年12月15日
    000
  • Beego缓存Redis JSON数据:取出JSON对象需要反序列化吗?

    Beego框架下Redis缓存JSON数据的读取方法 在使用Beego框架将JSON数据存储到Redis后,读取时是否需要进行JSON反序列化取决于存储数据的类型。 问题背景 文中提到,将JSON字符串存储到Redis后,无法正确解析为JSON对象。 问题根源 代码分析表明,JSON数据存储到Red…

    2025年12月15日
    000

发表回复

登录后才能评论
关注微信