Search Algorithm Notebook

19 阅读6分钟

Background

1 A*(A Star)Algorithm

A算法是一种常用于图搜索和路径规划的启发式搜索算法。它利用启发式函数(heuristic function)来评估每个节点的价值,并据此来指导搜索过程,以便在可能的搜索空间中尽快找到最优解。A算法结合了Dijkstra算法的无向图搜索和贪婪最优化搜索的优点,同时避免了它们的缺点。

算法思想

A*算法的基本思想包括以下几点:

  • 启发式函数(Heuristic Function):A*算法使用启发式函数来评估每个节点到目标节点的估计距离。这个启发式函数提供了一种启发式的估计,用来指导搜索的方向,使得算法更有可能沿着最有希望的路径前进。

  • 综合考虑路径代价:A*算法同时考虑了从起始节点到当前节点的实际代价(通常用G值表示)和从当前节点到目标节点的估计代价(通常用H值表示)。它通过计算这两个代价的和(F值)来选择要探索的下一个节点。

  • 贪婪搜索:A算法在每一步都选择F值最小的节点进行扩展,这使得算法更有可能朝着目标节点的方向前进。这种贪婪搜索的思想使得A算法更加高效。

A* 算法的一般表达式

假设我们有一个图,其中包含节点(nodes)和连接这些节点的边(edges)。每个边可能具有权重(cost)。

  • G(n)G(n):表示从初始节点到节点 nn 的实际代价(即起始节点到节点 nn 的路径长度)。
  • H(n)H(n):表示从节点 nn到目标节点的估计代价。这通常是一个启发式函数,用来估计从节点 nn 到目标节点的最短距离。这个函数在A*算法中至关重要。
  • F(n)=G(n)+H(n)F(n)=G(n)+H(n):表示通过节点 nn的估计总代价。

A*算法的基本原理是在搜索过程中,秉承贪婪算法思想每次选择 F(n)F(n) 最小的节点进行扩展。这样做可以在保证搜索最优解的同时,尽可能地减少搜索空间。

* 初始化open_list和close_list;
* 将起点加入open_list中,并设置优先级为0(优先级最高);
* 如果open_list不为空,则从open_list中选取优先级最高的节点n:
    * 如果节点n为终点,则:
        * 从终点开始逐步追踪parent节点,一直达到起点;
        * 返回找到的结果路径,算法结束;
    * 如果节点n不是终点,则:
        * 将节点n从open_list中删除,并加入close_list中;
        * 遍历节点n所有的邻近节点:
            * 如果邻近节点m在close_list中,则:
                * 跳过,选取下一个邻近节点
            * 如果邻近节点m也不在open_list中,则:
                * 设置节点m的parent为节点n
                * 计算节点m的优先级
                * 将节点m加入open_list中

A* 算法的演示例子

假设我们有一个网格地图,其中包含起始节点、目标节点和一些障碍物。我们的目标是找到从起始节点到目标节点的最短路径。

假设我们的地图如下所示:

S - - - - 
- X X - - 
- - - - - 
- X X X - 
- - - E - 

其中,S是起始节点,E是目标节点,X表示障碍物,-表示可以移动的空格。

我们可以用以下简化的距离来估计到目标节点的距离(H值):

  • 欧几里得距离(Euclidean distance):H(n)=(xexn)2+(yeyn)2)H(n)=\sqrt{(x_e-x_n)^2+(y_e-y_n)^2)}(xe,ye)(x_e,y_e)表示目标节点坐标,(xn,yn)(x_n,y_n)表示当前节点坐标。
  • 曼哈顿距离(Manhattan distance):H(n)=xexn+yeynH(n)=|x_e-x_n|+|y_e-y_n|

我们选择曼哈顿距离作为启发式函数。

接下来,我们按照A*算法的步骤进行搜索:

  1. 将起始节点加入开放列表,开始搜索过程。
开放列表(open list):[(1,1)]
闭合列表(closed list):[]

选择当前节点S,并将其从开放列表移除。检查其邻居节点:

当前节点S的邻居节点有:右边、下边。计算这些邻居节点的F、G、H值:

  • 右边节点:G=1(起始点到右边节点的实际代价),H=7(目标节点到右边节点的估计代价,使用曼哈顿距离),F=8。
  • 下边节点:G=1,H=6,F=7。

将这些节点加入开放列表,并选择F值最小的节点作为当前节点。则有:

开放列表:[(1,2), (2,1)]
闭合列表:[(1,1)]
- - - - - 
S X X - - 
- - - - - 
- X X X - 
- - - E - 
  1. 继续搜索。

当前节点下边节点的邻居节点有:右边、下边。计算这些邻居节点的F、G、H值:

  • 右边节点:G=2,H=6,F=8。
  • 下边节点:G=2,H=5,F=7。

更新右边节点的F值,并选择F值最小的节点作为当前节点。

开放列表:[右边节点]
闭合列表:[S, 下边节点]
当前节点:右边节点

继续搜索。

- - - - - 
- X X - - 
- - S - - 
- X X X - 
- - - E - 

右边节点的邻居节点有:下边。计算下边节点的F、G、H值:

  • 下边节点:G=3,H=5,F=8。 更新右边节点的F值,并选择F值最小的节点作为当前节点。
开放列表:[]
闭合列表:[S, 下边节点, 右边节点]
当前节点:下边节点

继续搜索,直到找到目标节点或者开放列表为空。 在本例中,A*算法会继续搜索,直到找到目标节点E为止。如果搜索失败,则表示不存在从起始节点到目标节点的路径。

这个例子说明了A*算法是如何在地图中搜索最短路径的过程。

A* 算法代码实现

# 定义节点类
class Node:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.g = 0
        self.h = 0
        self.f = 0
        self.parent = None
    

# 定义A*算法函数
def astar(grid, start, end):
    # 定义移动方向
    directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
    # 获取网格大小
    rows = len(grid)
    cols = len(grid[0])
    
    # 创建起始节点和目标节点
    start_node = Node(start[0], start[1])
    end_node = Node(end[0], end[1])
    
    # 创建开放列表和闭合列表
    open_list = []
    closed_list = set()
    
    # 将起始节点加入开放列表
    open_list.append(start_node)
    
    while open_list:
        # 弹出开放列表中f最小的节点
        current_node = min(open_list,key=lambda node:node.f)
        open_list.remove(current_node)
        # 将当前节点加入闭合列表
        closed_list.add((current_node.x, current_node.y))
        
        # 如果当前节点是目标节点,返回路径
        if current_node.x == end_node.x and current_node.y == end_node.y:
            path = []
            while current_node:
                path.append((current_node.x, current_node.y))
                current_node = current_node.parent
            return path[::-1]
        
        
        
        # 遍历当前节点的邻居节点
        for dx, dy in directions:
            new_x = current_node.x + dx
            new_y = current_node.y + dy
            
            # 确保邻居节点在网格范围内,且是可行节点,且不在闭合列表
            if 0 <= new_x < rows and 0 <= new_y < cols and grid[new_x][new_y] != 'X' and (new_x, new_y) not in closed_list:
                neighbor = Node(new_x, new_y)
                
                # 不能回到走过的地方
                if neighbor in closed_list:
                    break
                # 如果邻居节点不在开放列表中
                elif neighbor not in open_list:
                    # heapq.heappush(open_list, neighbor)
                    open_list.append(neighbor)
                    # 创建邻居节点
                    neighbor.g = current_node.g + 1
                    neighbor.h = abs(end_node.x - new_x) + abs(end_node.y - new_y)  # 曼哈顿距离作为启发式函数
                    neighbor.f = neighbor.g + neighbor.h
                    neighbor.parent = current_node

                
    # 如果开放列表为空但仍未找到路径,返回空列表
    return []

# 测试样例
if __name__ == "__main__":
    grid = [
        ['S', '-', '-', '-', '-'],
        ['-', 'X', '-', 'X', '-'],
        ['-', '-', '-', '-', '-'],
        ['X', '-', '-', '-', 'X'],
        ['-', '-', 'X', '-', '-'],
        ['-', 'X', '-', 'X', '-'],
        ['-', 'X', '-', 'X', '-'],
        ['-', '-', 'E', '-', '-'],
        ['-', '-', '-', '-', '-']
    ]
    
    start = (0, 0)
    end = (7, 2)
    
    path = astar(grid, start, end)
    if path:
        print("找到最短路径:", path)
    else:
        print("未找到路径")

其他资料

A*算法详解(个人认为最详细,最通俗易懂的一个版本)-CSDN博客