启发式搜索算法2 - A*算法

本文介绍了A*算法,一种在静态路网中高效求解最短路径的方法,通过结合确定性和启发式特性,通过估价函数h(n)的选取平衡搜索效率与最优解。通过实例和不同h(n)的选择探讨其原理和应用策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

相关文章:
启发式搜索算法1 – 最佳优先搜索算法

A* 算法

最佳优先搜索算法的效果非常依赖估价函数,而估价函数又不是这么容易设置,那么能不能折中一下,添加其他确定性参数来平衡这个估价函数的不确定性,这就是A算法。
A
算法(A-Star)是一种静态路网中求解最短路径最有效的直接搜索方法,也是解决许多搜索问题的有效算法。它是基于使用启发式方法来实现最佳性和完整性的,它是最佳优先算法的一种变体。因为它不但具备启发式搜索特点,而且能够保证找到最佳的解决方案。许多游戏和基于Web的地图都使用此算法非常有效地找到最短路径(近似值)。

为什么这个算法这么厉害,主要是它在进行搜索时候,会计算到达相邻结点的成本函数f(n),挑选f(n)最小结点。这个成本函数公式为f(n)=g(n)+h(n),g(n)函数是从起点状态到当前状态n的实际代价,这是一个确定值,h(n)是从当前状态n到目标状态的最佳路径的估计代价,换言之f(n)函数包含了一部分确定值和一部分估计值的估价函数,也可以说是一种启发式搜索算法。为了保证找到最短路径(最优解),关键在于估价函数f(n)的选取(或者说h(n)的选取),也可以说是g(n)和h(n)值的比重分配。以d(n)表达状态n到目标状态的实际距离,那么h(n)的选取大致有如下三种情况:

(1)如果h(n)<d(n)到目标状态的实际距离,或者说h(n)的比重过小,这种情况下,搜索的点数多,搜索范围大,效率低。但能得到最优解。在极端情况下,h(n)值过小可以忽略,f(n)约等于g(n),算法变回了盲目搜索。
(2)理想状态是h(n)=d(n),即距离估计h(n)等于实际最短距离,那么搜索效率是最高的。
(3)如果h(n)>d(n),或者说h(n)的比重过大,这种情况下搜索的点数少,搜索范围小,效率高,但不能保证得到最优解。在极端情况下,h(n)值过大,使得f(n)约等于h(n),算法变回了最佳优先算法。
举一个例子,参照上一个的例子,这次是一个有权有向图,【D-E】边值变成5,如图所示。

在这里插入图片描述
尝试用A*算法在图结构上的直接实现。为了简单起见,所有结点的启发式函数h(n)均定义为1,分析过程如下表所示。
在这里插入图片描述
每次挑选结点,都是在开放列表中选择f(n)最小值的结点,直到找到目标路径。从表中看到,f(F)的值经过一次修改,在选择【E】结点的时候,因为g(f)值减少了,f(F)也就比原来的值小,因此更新了【F】结点的f(n)值,以下是用代码来表示此算法运算。

class Graph(object):
    def __init__(self, graph):
        self.graph = graph 
    def get_neighbors(self, v):
        return self.graph[v]
    def h(self, n):
        # 估价函数,我们简化了这个函数,假设每个结点的距离为1
        H = {
            'A': 1,
            'B': 1,
            'C': 1,
            'D': 1,
            'E': 1,
            'F': 1,
            'G': 1,
            'H': 1,
        }
        return H[n]
    def a_star_algorithm(self, start_node, target_node):
        # A*算法主程序
        open_list = set([start_node]) # 在开放列表中,是结点已被访问,但邻接的结点未被访问
        closed_list = set([]) # 那是结点已访问,邻接的结点也都全部访问
        g = {} # 记录所有结点到开始结点的距离,若没有记录就当成无穷大
        g[start_node] = 0 # 开始结点与自身距离为零
        parents = {} # 记录结点的邻接结点
        parents[start_node] = start_node # 第一个结点为开始结点
        while len(open_list) > 0: # 直到开放列表为空,跳出循环
            n = None
            for v in open_list: # 寻找f(n)的最小值
                if n == None or g.get(v,MAX) + self.h(v) < g.get(n,MAX) + self.h(n):
                    n = v
            if n == None: # 找不到下一个结点,证明路径不存在
                print('两个结点没有路径相连')
                return None
            print("挑选结点:", n)
            # 如果到达目标结点,开始重建回路
            if n == target_node:
                reconst_path = []
                while parents[n] != n: # 直到找到开始结点,跳出循环
                    reconst_path.append(n)
                    n = parents[n]
                reconst_path.append(start_node) # 补充开始结点
                reconst_path.reverse() # 翻转列表
                print('最佳路径为: {}'.format(reconst_path))
                return reconst_path
            # 遍历该结点的所有邻接结点
            for (neighbor_node, value) in self.get_neighbors(n):
                if neighbor_node not in open_list and neighbor_node not in closed_list:
                    # 该结点不在开放列表和关闭列表,则加入开放列表
                    open_list.add(neighbor_node)
                    parents[neighbor_node] = n # 记录其父亲结点,便于构建路径
                    g[neighbor_node] = g[n] + value # 记录此结点到开始结点的代价
                else:
                    # 新的路径代价比原来小则更新路径
                    if g[neighbor_node] > g[n] + value: 
                        parents[neighbor_node] = n
                        g[neighbor_node] = g[n] + value
                        # 如果该结点在关闭列表,让他重新回到开放列表
                        if neighbor_node in closed_list:
                            closed_list.remove(neighbor_node)
                            open_list.add(neighbor_node)
            open_list.remove(n) # 所有邻接结点访问完,移除该结点
            closed_list.add(n) # 放到关闭列表
        # 尝试所有可能后,若没有找到路径,说明不存在
        print('两个结点没有路径相连')
        return None

创建一个【Graph】类,继续使用邻接列表来表示图,属性【graph】保存图的邻接列表,属性【parents】是记录结点的父结点,用于从目标结点回溯构建最优解的路径。a_star_algorithm()函数是A*算法主程序,算法实现步骤如下。
(1)初始化开放列表【open_list】,把起点结点放入开放列表。
(2)初始化关闭列表【closed_list】为空列表。
(3)若开放列表不为空,进入循环来到第四步,否则程序结束到第十一步。
(4)在开放列表中挑选f(n)最小的结点n。
(5)若n是目标结点,就结束循环,如不是则进入第六步。
(6)遍历n结点的所有邻接结点neighbor_node,进入第七步,遍历结束来到第十步。
(7)若邻接结点不在开放列表也不在关闭列表,进入第八步,否则进入第九步。
(8)把邻接结点放进开放列表,更新g(neighbor_node)的值。
(9)若新的g(neighbor_node)小于原来的值,则更新值,若此结点在关闭列表,则把它从关闭列表移除,并放到开放列表中。
(10)把结点n从开放列表移除,添加到关闭列表。
(11)结束程序。
程序的关键部分是第四步,计算f(n)的值,找到最小值的结点。这里主要是介绍算法思路,所以简化了f(n)的计算,h(n)变成一个固定值1,现在来测试一下程序。

graph_list = {
    "A": [("B",3), ("D",2), ("G",12)],
    "B": [("H",9)],
    "D": [("C",4), ("E",5)],
    "C": [("F",3)],
    "E": [("F",1)],
}
graph = Graph(graph_list)
graph.a_star_algorithm('A', 'F')
# ---------结果-------------
挑选结点: A
挑选结点: D
挑选结点: B
挑选结点: C
挑选结点: E
挑选结点: F
最佳路径为: ['A', 'D', 'E', 'F']

这里打印了程序运行过程,挑选结点的顺序和手动调试是一致的,而且最后结果也是符合预期。这个例子中的h(n)估计函数的作用不是很大,但也能体现算法的思想。当遇到实际问题的时候,要根据实际情况来设置h(n),比如在一个方格上的寻找路径,如下图所示。
在这里插入图片描述
h(n)的值可以是两个坐标值的曼哈顿距离(Manhattan Distance),h(n)为目标的x和y坐标与当前单元格的x和y坐标之差的绝对值之和,正如图中的黑色线条,或者取目标的x和y坐标与当前单元格的x和y坐标之差的绝对值的最大值,正如图中的蓝色线条,又或者计算两个坐标点的直线距离,如图中的红色线条。

注意:在选择h(n)的时候也要考虑运算效率,比如红色线条的计算涉及平方和开方,相对其他两种只有加减运算就复杂了。

更多内容

想获取完整代码或更多相关图的算法内容,请查看我的书籍:《数据结构和算法基础Python语言实现》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值