图遍历算法之最低成本路径搜寻算法Fringe Search

注:本文为文献翻译

一、前言

路径搜索(pathfinding)是许多代理应用程序的核心组件,包括商用计算机、机器人设计。但对于许多应用程序,特别是具有严格实时性要求的应用程序,用于路径寻找的算法周期长、CPU资源消耗巨大。因此,需要设计更为高效的路径寻找算法。
本文关注的应用是基于网格的路径搜索算法,代理必须遍历二维空间,从网格的一个点移动到另外一个点消耗一定的成本。路径搜索的目标是:从网格的起始点到目标位置的路径成本最小化。例如,在许多商业计算机游戏中,路径寻找是计算机代理的基本要求。对于实时策略游戏,可能同时有上百个代理进行交互,每个代理都有路径搜索需求。基于网格的路径搜索是应用核心,实时性的要求需要算法进行重新设计。
搜索或其变体算法是单一代理优化搜索算法,算法执行最佳优先搜索策略,算法采用深度优先策略。相比于,采用开放和封闭列表存储方式,构建的搜索树更小,而算法使用的存储线性正比于最短路径长度。然而,两种算法都存在缺点,算法最佳搜索不是免费的,维护列表的排序非常昂贵,算法的低存储解决方案也不是免费提供的,算法会多次访问相同节点。此外算法相比于,更易于实现。
算法因存储需要付出较大代价,尤其是包括圈或重复状态(例如网格)的搜索空间,深度优先搜索机制会访问节点所有不同路径。算法相比于算法具备以下三个优势

  1. 算法无法检测重复状态(例如网格),算法可以通过开放和封闭列表包含的信息避免重复搜索工作;
  2. 因为算法重建搜索边界,其迭代加深会导致重复搜索同一状态。算法的开放列表可以维护搜索边界;
  3. 算法采用从左到右的搜索,算法按照排序顺序维护搜索边界,以最佳方式扩展节点。
    算法的第一个问题于1994年由Reinefeld和Marsland提出的转换表得到解决。通过固定大小的数据结构(通常用哈希表实现)存储搜索结果。在搜索下一节点之前,算法查询该哈希表来决定是否需要对该节点做进一步搜索。
    算法的第二、三个问题可通过提出Fringe Search算法来代替算法和算法得到解决。算法通过深度优先搜索来构造每次搜索迭代过程中考虑的节点集合。通过跟踪这些节点,可以降低迭代加深的开销。此外,节点不需要事先进行排序存储。在极端情况下,Fringe Search算法既可以与算法以相同的顺序访问节点,又可以扩展成算法采用的节点排序机制。

本文的主要贡献如下

  1. 介绍Fringe Search算法,提出时间/空间平衡的全新搜索算法,以克服算法和算法的不足;
  2. 通过实验评估算法、算法以及Fringe Search算法。在评估过程中发现,计算机游戏路径搜索问题中,Fringe Search算法比优化版本的算法节约更多的时间;
  3. 比较搜索算法的性能,标准的算法和优化的算法存在巨大的性能差异,性能差的算法的实施可能会导致误导性的实验结论;
  4. Fringe Search算法设计成通用的搜索框架,并包含多个重要的单代理搜索算法。

二、Fringe Search搜索算法

标准的单代理表示法
:从起始节点到当前节点的搜索成本;
:当前节点到目标节点的成本估计;
:从起始点到目标节点的预估搜索总成本;
:从起始点到目标节点的真实搜索成本。
算法的工作原理
给定起始阈值,算法执行递归的从左到右深度优先搜索,当找到目标点或大于的节点时递归停止。若给定阈值的搜索过程中,未找到目标节点,则增加阈值并进行重新搜索(算法在阈值上迭代)。
与算法相比,算法存在三个方面的效率问题,现一一介绍如下:

2.1 重复状态搜索

当在网格上进行路径搜索时,对于给定节点可能存在多条路径。使用转置表作为访问状态的缓存,可以解决重复状态问题。转置表通常为大的哈希表,从而最小化节点查找的成本。每次节点访问都需查转置表,可能会导致搜索不必要的子树。转置表可用于存储当前节点最小的值,以及搜索该节点产生的备份值。可用于剔除非最佳路径,值可用于表示针对当前迭代阈值,在该节点的搜索是不必要的。在本文中,算法通过转置表实现了内存增强版算法()。

2.2 迭代

算法每次迭代都重复上次迭代的所有搜索,由于算法基本不使用存储,因此这是必要的。

图1. IDA*算法与Fringe Search示例比较

考虑图1,图中每个分支都标有路径成本(1或2),启发函数是到达树根底部所需的移动次数(每次移动的成本为1)。左列阐述了算法的工作原理
第一轮搜索:以阈值开始,在算法无法证明可搜索到目标节点之前,在成本限制为4的情况下,两个节点被扩展(黑色圆圈)并访问了另外两个节点(灰色圆圈)。扩展节点同时生成子节点。
第二轮搜索:阈值增加为5时,搜索重新开始,每次迭代都构建深度优先搜索,从起始节点开始,递归直到超过阈值或找到目标节点。
第三轮搜索:增加阈值为6时,搜索重新开始,深度优先搜索遍历所有节点,并找到目标节点。
该算法必须重复前一次迭代的所有搜索以重建搜索的边界。在分支较小的情况,迭代成本会占据算法总执行时间。在此示例中,总共需要扩展17个节点,并访问27个节点。
图1中间列展示了Fringe Search算法的工作原理。
Fringe Search算法原理:在初始时,算法与相同,进行节点扩展和节点访问。保存第一次迭代的两个叶节点(灰色圆圈)作为第二次迭代的起始点。第二次迭代中,保存第二次迭代的三个叶节点(灰色圆圈)作为第三个迭代的初始点。在最后一次迭代中,算法只访问尚未访问的部分。在此示例中,Fringe Search算法共扩展了9个节点,访问了19个节点。由于扩展节点比访问节点更为昂贵,对进行了重大改进。
Fringe Search使用的数据结构为两个列表:一个用于存储当前迭代状态(now),另外一个用于存储下一次迭代的起始点(later)。初始状态,now列表存储根节点,later列表为空。该算法重复执行以下操作,检查now列表头部节点,并判断执行下列哪一个操作:

  • 若大于当前阈值,则从now列表中移除头部节点,并放置于later列表的尾部。该节点作为当前迭代访问的节点(灰色圆圈)用于下一次迭代。
  • 若小于或等于当前阈值,则考虑now列表头部节点的子节点(扩大头部节点),将子节点添加至now列表的头部,之前头部节点舍弃。

当迭代完成且搜索目标未找到时,则增加搜索阈值。later列表变为now列表,later列表设置为空。
当now列表节点被扩展时,子节点可以以任意顺序添加进now列表。若子节点按照从左到右的顺序插入到列表(最左边的节点始终在now列表的前端),则Fringe Search与采用相同的扩展节点机制。子节点的添加可以以任意顺序开展,从而衍生出不同的算法。

2.3 排序

采用了从左到右的遍历机制,算法则将节点继续排序,通过最佳优先遍历机制进行节点扩展。上述介绍的Fringe Search节点没有进行排序-节点要么在now列表或在later列表。通过多个later列表设计,Fringe Search算法可以改成排序算法。在极端情况下,通过对每个可能是值进行存储,Fringe将与算法拥有相同的节点扩展排序。Fringe Search算法无需对now列表进行排序。这样,算法在没有排序时,可以按照的方式将节点从左到右排序;算法需进行排序时,可以按照算法进行节点最佳排序。当介于两者之间时,通过设计多个存储列表,使用最佳搜索机制而无需算法大量的开销。

2.5 Fringe Search算法的实施

Fringe Search算法的伪代码如下图所示。为了使算法更快的运行,进行了几项增强(也适用于 和 ):

  • now列表和later列表设计为单个双链表,当前节点之前的节点存在于later列表,剩余节点存在now列表。
  • 网格中每个节点的预分配列表节点存成一个数组,从而得知节点固定访问时间。
  • 标记数组被用于固定时间查找,以确定节点是否在列表中。
  • 值和迭代数据缓存用哈希表进行存储。
  • 标记数组用于固定时间查找,以确定节点是否被访问并且检查缓存中条目是否有效。
g:  the cost to reach current node
h(node): estimated cost of the cheapest path (node..goal)
f=g+h: estimated cost of the cheapest path (root..node..goal)
cost(node, child): step cost function

init(start, goal)
    fringe F = s # later 列表, s为起始点
    cache C[start] = (0, null)  #缓存哈希表(当前搜索成本,父节点)
    C[n]=null for n not equal to start
    flimit = h(start) #初始阈值
    found = false #初始状态,目标节点未找到

    while (found == false) AND (F not empty)
        fmin = ∞ #网格很多路径可达目标点,故存在成本最低路径
        for n in F, from left to right
            (g, parent) = C[n]
            f = g + h(n)
            if f > flimit
                fmin = min(f, fmin)
                continue
            if n == goal
                found = true
                break
            for child in children(n), from right to left #对节点进行访问排序
                g_child = g + cost(n, child) # cost(n, child)是step cost function
                if C[child] != null
                    (g_cached, parent) = C[child]
                    if g_child >= g_cached
                        continue
                if child in F
                    remove child from F
                insert child in F past n
                C[child] = (g_child, n)
            remove n from F
        flimit = fmin # F不断更新,所以f_min会不断更新

    if reachedgoal == true
        reverse_path(goal)

回溯节点伪代码

reverse_path(node)
    (g, parent) = C[node]
    if parent != null
        reverse_path(parent)
    print node

参考文献:

[1] Björnsson, Yngvi; Enzenberger, Markus; Holte, Robert C.; Schaeffer, Johnathan. Fringe Search: Beating A* at Pathfinding on Game Maps. Proceedings of the 2005 IEEE Symposium on Computational Intelligence and Games (CIG05). Essex University, Colchester, Essex, UK, 4–6 April, 2005. IEEE 2005;
[2] Fringe Search, Wiki: https://en.wikipedia.org/wiki/Fringe_search.

你可能感兴趣的:(图遍历算法之最低成本路径搜寻算法Fringe Search)