当前位置: 首页 > article >正文

人工智能实验(四)-A*算法求解迷宫寻路问题实验

零、A*算法学习参考资料

1.讲解视频

A*寻路算法详解 #A星 #启发式搜索_哔哩哔哩_bilibili

2.A*算法学习网站

A* 算法简介

一、实验目的

熟悉和掌握A*算法实现迷宫寻路功能,要求掌握启发式函数的编写以及各类启发式函数效果的比较。

二、实验要求

同课本 附录B 实验指导书 实验五的指导要求和实验报告要求:

1.画出用A*算法求解迷宫最短路径的流程图。

2.设置不同的地图,以及不同的初始状态和目标状态,记录A*算法的求解结果,包括最短路径、扩展节点数、生成结点数和算法运行时间。

3.对于相同的初始状态和目标状态,设计不同的启发式函数,比较不同启发式函数对迷宫寻路速度的提升效果,包括扩展节点数、生成结点数和算法运行时间。

实验内容

迷宫问题是在以方格表示的地图场景中,对于给定的起点、终点和障碍物,如何找到一条从起点开始避开障碍物到达终点的最短路径。 假设在一个n*m的迷宫里,入口坐标和出口坐标分别为(1,1)和(5,5),每一个坐标点有两种可能:0和1,其中0表示该位置允许通过,1表示该位置不允许通过。

三、实验材料

A*算法的基本原理:

A*算法在搜索过程中,考虑了每个节点到起点的实际代价(g(n))和从该节点到目标的估算代价(h(n))。它使用以下公式来计算每个节点的评估代价:

f(n)=g(n)+h(n)

g(n):从起点到节点n的实际代价,通常是路径长度或其他度量。

h(n):从节点n到目标节点的估算代价,通常通过启发式方法来计算。常见的启发式函数包括曼哈顿距离、欧几里得距离等。

f(n):节点n的总代价,它是g(n)和h(n)的和。A*算法会优先选择具有最小f值的节点进行扩展。

A*算法的步骤:

1. 初始化:将起点加入开放列表(Open List),并设置其g和h值。起点的f值为g + h。

2. 循环搜索:

3. 目标找到:当目标节点被加入闭合列表时,路径搜索结束。此时,可以从目标节点回溯到起点,得到最短路径。

4. 结束:如果开放列表为空,表示没有路径可达目标。

特点:

启发式:A*算法通过启发式函数来引导搜索过程,使得搜索在达到目标时更为高效。

最优性:只要启发式函数是可接受的(即不高估到目标的代价),A*算法可以保证找到最短路径。可接受的启发式函数通常需要满足一致性或单调性条件。

启发式函数的选择:

启发式函数h(n)的选择对A*算法的性能和效率有重要影响。常见的启发式函数包括:

曼哈顿距离:适用于格子状地图,只能横向或纵向移动。

欧几里得距离:适用于平面上的直线距离。

四、实验步骤

1.画出A*算法求解迷宫最短路径问题的流程图。

2.分析不同启发式函数h(n)对迷宫寻路求解的速度提升效果。

3.分析A*算法求解不同规模迷宫最短路径问题的性能。

4.提交源程序。

5.总结实验心得体会。

五、思考与问答

问:A*算法如何处理迷宫中的障碍物?

答:在A*算法中,每次扩展节点时,都需要检查其邻居节点是否为障碍物。如果邻居是障碍物,算法就会跳过这个邻居,不将其加入开放列表。障碍物被认为是无法通过的区域,因此它们在路径搜索过程中是不可达的。

问:A*算法保证找到最短路径吗?

答:是的,只要启发式函数是“可接受的”,即不高估从当前节点到目标节点的实际代价,A算法就能保证找到最短路径。对于迷宫问题,常见的启发式函数(如曼哈顿距离、欧几里得距离等)是可接受的,因此A可以找到从起点到终点的最短路径。

问:A*算法的效率如何?如何应对大规模迷宫?

答:A算法的效率取决于迷宫的复杂度和启发式函数的设计。在大规模迷宫中,A算法的时间复杂度和空间复杂度可能很高,因为需要维护开放列表和闭合列表。在这种情况下,可以采取以下优化策略:

- 启发式函数优化:选择合适的启发式函数,如曼哈顿距离、欧几里得距离等,以缩小搜索范围。

- 迭代加深A*(IDA*):通过限制搜索深度,减少内存消耗。

- 跳跃搜索:在某些情况下,通过跳过某些中间步骤,减少计算量。

六、源代码

'''from queue import PriorityQueue

def a_star_search(graph,start,goal):
    """
    使用 A* 搜索算法在图中寻找从起点到终点的路径。

    参数:
        graph (Graph): 要搜索的图。
        start: 搜索的起点。
        goal: 搜索的终点。

    返回:
        tuple: 包含came_from和cost_so_far的元组。
            came_from (dict): 一个字典,存储了每个节点的前一个节点,用于回溯路径。
            cost_so_far (dict): 一个字典,存储了到每个节点的当前最小代价。
    """
    # 使用优先级队列 frontier 来存储待探索的节点,优先级由估计的总代价决定
    frontier = PriorityQueue()
    # 将起点 start 放入 frontier 中,其优先级为 0
    frontier.put(start, 0)

    # 创建 came_from 字典,用于记录每个节点的前一个节点
    came_from = dict()
    # 创建 cost_so_far 字典,用于记录到每个节点的当前最小代价
    cost_so_far = dict()

    # 将起点 start 的前一个节点设置为 None,并将其代价设置为 0
    came_from[start] = None
    cost_so_far[start] = 0

    # 当 frontier 不为空时,循环执行以下操作
    while not frontier.empty():
        # 从 frontier 中取出具有最小代价的节点 current
        current = frontier.get()

        # 如果 current 节点等于目标节点 goal,则搜索结束
        if current == goal:
            break

        # 遍历 current 节点的所有邻居节点 next
        for next in graph.neighbors(current):
            # 计算从起点到 next 节点的新代价 new_cost
            new_cost = cost_so_far[current] + graph.cost(current, next)
            # 如果 next 节点没有被访问过,或者通过 current 节点到达 next 节点的代价更小
            if next not in cost_so_far or new_cost < cost_so_far[next]:
                # 更新到 next 节点的代价为 new_cost
                cost_so_far[next] = new_cost
                # 计算从起点到 next 节点的估计总代价 priority
                priority = new_cost + heuristic(goal, next)
                # 将 next 节点及其估计总代价 priority 放入 frontier 中
                frontier.put(next, priority)
                # 将 next 节点的前一个节点设置为 current
                came_from[next] = current

    # 返回 came_from 和 cost_so_far 字典,用于回溯路径和计算总代价
    return came_from, cost_so_far


def heuristic(a, b):
    """
    计算两个点 a 和 b 之间的曼哈顿距离。

    参数:
        a (tuple): 第一个点的坐标,格式为 (x1, y1)。
        b (tuple): 第二个点的坐标,格式为 (x2, y2)。

    返回:
        int: 两点之间的曼哈顿距离。
    """
    # 解包元组 a 得到 x1 和 y1
    (x1, y1) = a
    # 解包元组 b 得到 x2 和 y2
    (x2, y2) = b
    # 计算 x 坐标之差的绝对值
    x_diff = abs(x1 - x2)
    # 计算 y 坐标之差的绝对值
    y_diff = abs(y1 - y2)
    # 返回曼哈顿距离,即 x 和 y 坐标之差的绝对值之和
    return x_diff + y_diff


'''
# A* 算法代码

from queue import PriorityQueue
import time
import math

class MazeGraph:
    """
    迷宫图的表示类,包含迷宫的行列数据,以及获取邻居节点和路径代价的方法。
    """

    def __init__(self, maze):
        """
        初始化 MazeGraph 对象。

        参数:
            maze (list of lists): 二维列表,代表迷宫的布局。

        返回:
            None
        """
        # 将传入的迷宫赋值给实例变量 self.maze
        self.maze = maze
        # 获取迷宫的行数,并赋值给实例变量 self.rows
        self.rows = len(maze)
        # 获取迷宫的列数,并赋值给实例变量 self.cols
        self.cols = len(maze[0])


    def neighbors(self, node):
        """返回当前节点的所有邻居节点"""
        # 获取节点的 x 和 y 坐标
        x, y = node
        # 初始化邻居节点列表
        neighbors = []
        # 定义四个方向的偏移量:左、右、上、下
        directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
        # 遍历每个方向
        for dx, dy in directions:
            # 计算邻居节点的坐标
            nx, ny = x + dx, y + dy
            # 检查邻居节点是否在迷宫范围内,并且不是墙壁(值为 0)
            if 0 <= nx < self.rows and 0 <= ny < self.cols and self.maze[nx][ny] == 0:
                # 如果是有效邻居节点,则将其添加到列表中
                neighbors.append((nx, ny))
        # 返回所有有效的邻居节点
        return neighbors


    def cost(self, from_node, to_node):
        """
        返回从 from_node 到 to_node 的路径代价,这里代价为 1(单位代价)

        参数:
            from_node (tuple): 起点坐标。
            to_node (tuple): 终点坐标。

        解释:
            行走每一个格子的代价为 1(单位代价)
        """
        return 1



def a_star_search(graph, start, goal):
    """
    使用 A* 搜索算法在图中寻找从起点到终点的路径。

    参数:
        graph (MazeGraph): 迷宫图对象。
        start: 搜索的起点。
        goal: 搜索的终点。

    返回:
        tuple: 包含came_from和cost_so_far的元组。
            came_from (dict): 一个字典,存储了每个节点的前一个节点,用于回溯路径。
            cost_so_far (dict): 一个字典,存储了到每个节点的当前最小代价。
    """
    # 使用优先级队列 frontier 来存储待探索的节点,优先级由估计的总代价决定
    frontier = PriorityQueue()
    frontier.put((0, start))  # 将起点 start 放入 frontier 中,其优先级为 0

    # 创建 came_from 字典,用于记录每个节点的前一个节点
    came_from = dict()
    # 创建 cost_so_far 字典,用于记录到每个节点的当前最小代价
    cost_so_far = dict()

    # 将起点 start 的前一个节点设置为 None,并将其代价设置为 0
    came_from[start] = None
    cost_so_far[start] = 0

    # 记录扩展节点数和生成节点数
    expand_count = 0
    generate_count = 0

    # 记录起始时间
    start_time = time.time()

    while not frontier.empty():
        current_cost, current = frontier.get()
        expand_count += 1   # 扩展节点数加一

        # 如果 current 节点等于目标节点 goal,则搜索结束
        if current == goal:
            break

        # 遍历 current 节点的所有邻居节点 next
        for next in graph.neighbors(current):
            # 计算从起点到 next 节点的新代价 new_cost
            new_cost = cost_so_far[current] + graph.cost(current, next)
            # 如果 next 节点没有被访问过,或者通过 current 节点到达 next 节点的代价更小
            if next not in cost_so_far or new_cost < cost_so_far[next]:
                # 更新到 next 节点的代价为 new_cost
                cost_so_far[next] = new_cost

                # 在这选择估价曼哈顿算法
                # heuristic = heuristic_manhattan(next, goal)

                # 在这选择估价欧几里得算法
                heuristic = heuristic_euclidean(next, goal)

                priority = new_cost + heuristic  # fn = gn + hn


                # 将 next 节点及其估计总代价 priority 放入 frontier 中
                frontier.put((priority, next))
                # 将当前结点更新在came_from中
                came_from[next] = current
                generate_count += 1 # 生成节点数

    # 计算运行时间
    end_time = time.time()
    execution_time = end_time - start_time

    return came_from, cost_so_far, expand_count, generate_count, execution_time


def heuristic_manhattan(a, b):
    """
    计算两个点 a 和 b 之间的曼哈顿距离。

    参数:
        a (tuple): 第一个点的坐标,格式为 (x1, y1)。
        b (tuple): 第二个点的坐标,格式为 (x2, y2)。

    返回:
        int: 两点之间的曼哈顿距离。
    """
    (x1, y1) = a
    (x2, y2) = b
    return abs(x1 - x2) + abs(y1 - y2)



def heuristic_euclidean(a, b):
    """
    计算两个点 a 和 b 之间的欧几里得距离。

    参数:
        a (tuple): 第一个点的坐标,格式为 (x1, y1)。
        b (tuple): 第二个点的坐标,格式为 (x2, y2)。

    返回:
        float: 两点之间的欧几里得距离。
    """
    (x1, y1) = a
    (x2, y2) = b
    return math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)





def reconstruct_path(came_from, start, goal):
    """
    从 came_from 字典中回溯路径。

    参数:
        came_from (dict): 包含每个节点的前一个节点的字典。
        start: 起点。
        goal: 终点。

    返回:
        list: 从 start 到 goal 的路径。
    """
    # 初始化路径列表
    path = []
    # 将当前节点设置为终点
    current = goal
    # 当当前节点不等于起点时,循环执行以下操作
    while current!= start:
        # 将当前节点添加到路径中
        path.append(current)
        # 从 came_from 字典中获取当前节点的前一个节点,并更新当前节点
        current = came_from[current]
    # 将起点添加到路径中
    path.append(start)
    # 反转路径,使其从起点到终点
    path.reverse()
    # 返回重构的路径
    return path



def print_map(maze):
    """
    打印迷宫地图。

    参数:
        maze (list of lists): 二维列表,代表迷宫的布局。

    返回:
        None
    """
    # 遍历迷宫的每一行
    for row in maze:
        # 使用 join 方法将每一行的单元格值连接成一个字符串,单元格之间用空格分隔
        print(" ".join(str(cell) for cell in row))


def mark_path_on_map(maze, path):
    """
    在迷宫中标记路径,使用字母 A, B, C 等。

    参数:
        maze (list of lists): 二维列表,代表迷宫的布局。
        path (list of tuples): 路径上的点的列表,每个点是一个 (x, y) 坐标对。

    返回:
        list of lists: 修改后的迷宫地图,其中路径上的点被标记为字母。
    """
    # 初始化字母为 'A'
    letter = 'A'
    # 遍历路径上的每个点
    for (x, y) in path:
        # 包括起点和终点
        maze[x][y] = letter
        # 更新字母到下一个
        letter = chr(ord(letter) + 1)
    # 返回标记路径后的迷宫地图
    return maze

import random

def generate_maze(rows, cols, obstacle_probability=0.3):
    # 初始化迷宫地图,所有单元格默认为可通行(0)
    maze = [[0 for _ in range(cols)] for _ in range(rows)]

    # 定义起点和终点
    start = (0, 0)
    end = (rows - 1, cols - 1)

    # 使用深度优先搜索生成从起点到终点的路径
    def dfs(x, y):
        stack = [(x, y)]
        visited = set()
        visited.add((x, y))
        while stack:
            cx, cy = stack.pop()
            # 随机打乱邻居顺序
            neighbors = [(cx + dx, cy + dy) for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]]
            random.shuffle(neighbors)
            for nx, ny in neighbors:
                if 0 <= nx < rows and 0 <= ny < cols and (nx, ny) not in visited:
                    if (nx, ny) == end or dfs(nx, ny):
                        maze[cx][cy] = 0
                        maze[nx][ny] = 0
                        return True
        return False

    # 生成路径
    dfs(*start)

    # 随机生成障碍物
    for i in range(rows):
        for j in range(cols):
            if (i, j) != start and (i, j) != end and maze[i][j] == 0:
                if random.random() < obstacle_probability:
                    maze[i][j] = 1

    return maze

def main():

    '''    # 定义迷宫地图,0 表示可通行,1 表示障碍物
    maze = [
        [0, 0, 0, 0, 0],
        [1, 0, 1, 0, 1],
        [0, 0, 1, 1, 1],
        [0, 1, 0, 0, 0],
        [0, 0, 0, 1, 0]
    ]'''

    '''
    # 测试样例1:无解
    maze = [
    [0, 0, 0, 0, 0],
    [1, 1, 1, 0, 1],
    [0, 1, 1, 1, 0],
    [0, 1, 0, 1, 0],
    [0, 0, 0, 1, 0]
]
'''

    '''
    # 测试样例2:
    maze = [
    [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]
]
'''
    '''
    # 测试样例3:
    maze = [
    [0, 1, 0, 0, 0],
    [0, 1, 0, 1, 0],
    [0, 1, 0, 1, 0],
    [0, 1, 0, 1, 0],
    [0, 0, 0, 0, 0]
]
    
    '''
    maze = generate_maze(10, 10)

    # 设置起点和终点坐标
    start = (0, 0)  # 起点 (1,1)
    end = (9, 9)  # 终点 (5,5)

    # 创建迷宫图对象
    graph = MazeGraph(maze)

    # 调用 A* 搜索算法,返回 came_from 字典和 cost_so_far 字典
    came_from, cost_so_far, expand_count, generate_count, execution_time = a_star_search(graph, start, end)

    # 如果找到路径
    if end in came_from:
        # 重构路径
        path = reconstruct_path(came_from, start, end)
        # 打印最短路径
        print("最短路径: ", [f"({p[0] + 1},{p[1] + 1})" for p in path])

        # 在迷宫地图上标记路径
        marked_maze = mark_path_on_map([row[:] for row in maze], path)
        # 打印带路径标记的迷宫地图
        print("迷宫地图带路径标记:")
        print_map(marked_maze)

        # 输出扩展节点数、生成节点数、算法运行时间
        print(f"扩展节点数: {expand_count}")
        print(f"生成节点数: {generate_count}")
        print(f"算法运行时间: {execution_time:.30f} 秒")
    else:
        # 如果没有找到路径,打印提示信息
        print("没有找到路径")




if __name__ == "__main__":
    main()

七、结果


http://www.kler.cn/a/504512.html

相关文章:

  • Flink系统知识讲解之:容错与State状态管理
  • 【C】初阶数据结构3 -- 单链表
  • 网络安全概述
  • ElasticSearch|ES|架构介绍|原理浅析
  • 怎么实现Redis的高可用?
  • 【汇编】x86汇编编程寄存器资源心中有数
  • Vue.js组件开发-使用地图绘制轨迹
  • 互联网架构困境:TCP/IP 拥塞难题与地址对称性
  • 九 RK3568 android11 MPU6500
  • what?ngify 比 axios 更好用,更强大?
  • mysql 查询语句的执行流程
  • 【Java】设计模式——代理模式
  • 【记录】篡改猴插件下载网页m3u8视频
  • 如何监控 Elasticsearch 集群健康状态并实现邮件自动预警?
  • R 语言科研绘图 --- 折线图-汇总
  • 代码随想录day03
  • 信息时代的消费者行为变迁与应对策略:基于链动2+1模式、AI智能名片及S2B2C商城小程序的分析
  • Spring Boot Web技术栈(官网文档解读)
  • Opencv之模板匹配可视化
  • Flutter:使用FVM安装多个Flutter SDK 版本和使用教程
  • java -jar启动项目报错:XXX.jar中没有主清单属性
  • 小游戏前端地区获取
  • Django基础之ORM初识
  • Windows图形界面(GUI)-QT-C/C++ - Qt图形绘制详解
  • 长安“战疫”网络安全公益赛的一些随想
  • 基础理论知识:无人机图数传数据链技术详解