A *的变体:最大深度,多目标和多路径

时间:2018-03-12 14:22:59

标签: python algorithm graph-theory path-finding

此问题涉及A*算法及其三种变体:

  1. max-depth(仅在远离起始顶点的固定深度内搜索)
  2. 多目标(搜索多个目标而非单个目标)
  3. 多路径(搜索第一个 n 路径 - 可能共享一些顶点 - 从开始到目标)
  4. 为了解决我的问题,我将使用python(v3 +),因为它有助于提高可读性。有许多图形数据结构(AM,AL,DOK,CRS等)。由于A*仅对顶点集执行操作(假设顶点集具有前驱和后继的本地知识),我将使用邻接列表...或更具体地说是邻接字典(散列),例如:

    vertex_set = {
                    "a": {"b", "c"},
                    "b": {"c"}, 
                    ...
                 }
    

    为了提供一个启动点,下面提供了一个简单A*的实现:

    A *

    助手

    from math import inf
    
    def a_star_distance_between(start, goal):
        # cost_estimate may go here
        # but I set it to a constant zero transforming A* to a greedy breadth first
        # search as this estimate will vary by your use case
        cost_estimate = 0 
        return cost_estimate
    
    def reconstruct_path(came_from, current):
        total_path = [current]
        while current in came_from.keys():
            current = came_from[current]
            total_path.append(current)
        total_path.reverse()
        return total_path
    
    def a_star_heuristic_between(a, b):
        # your heuristic goes here
        # dummy heuristic just for functionality
        heuristic = len(vertex_set[a]) + len(vertex_set[b])
        return 1 / heuristic
    
    def a_star_lowest_f_score(f_scores, nodes_currently_discovered):
        f_min_val = inf
        f_min_key = ""
        for node in nodes_currently_discovered:
            val = f_scores[node]
            if val < f_min_val:
                f_min_key = node
                f_min_val = val
        return f_min_key
    

    A *正常

    def a_star(start, stop):
        nodes_already_evaluated = set() # a.k.a. closed set 
        nodes_currently_discovered = {start} # a.k.a. open set
    
        came_from = dict()
    
        # for each node, cost of getting from start node to that node
        g_score = {v: inf for v in list(vertex_set.keys())}
        g_score[start] = 0
    
        # for each node, cost of getting from the start node to the goal by passing through that node
        f_score = {v: inf for v in list(vertex_set.keys())}
        f_score[start] = 1 # normally a_star_heuristic_between(start, stop), 1 here because of hard coded value above
    
        while nodes_currently_discovered:
            current = a_star_lowest_f_score(f_score, nodes_currently_discovered)
            if current == stop:
                return reconstruct_path(came_from, current)
    
            nodes_currently_discovered.remove(current)
            nodes_already_evaluated.add(current)
    
            for neighbor in vertex_set[current]:
                if neighbor in nodes_already_evaluated:
                    continue
                if neighbor not in nodes_currently_discovered:
                    nodes_currently_discovered.add(neighbor)
                tentative_g_score = g_score[current] + a_star_distance_between(current, neighbor)
                if tentative_g_score >= g_score[neighbor]:
                    continue # not a better path
    
                # best path until now
                came_from[neighbor] = current
                g_score[neighbor] = tentative_g_score
                f_score[neighbor] = g_score[neighbor] + a_star_heuristic_between(neighbor, stop)
    
    在上面提到的三个变体中,从上面的实现中最容易做到的是(2)多目标:

    A *多目标

    from statistics import mean
    def multi_target_a_star(start, stops):
    
        # CHANGE
        stops_paths = {stop: None for stop in stops}
    
        nodes_already_evaluated = set() # a.k.a. closed set 
        nodes_currently_discovered = {start} # a.k.a. open set                                
    
        came_from = dict()
    
        # for each node, cost of getting from start node to that node
        g_score = {v: inf for v in list(vertex_set.keys())}
        g_score[start] = 0
    
        # for each node, cost of getting from the start node to the goal by passing through that node
        f_score = {v: inf for v in list(vertex_set.keys())}
        f_score[start] = 1 # normally a_star_heuristic_between(start, stop), 1 here because of hard coded value
    
        while nodes_currently_discovered:
            current = a_star_lowest_f_score(f_score, nodes_currently_discovered)
            if current == stop:
                # CHANGE
                stop_paths[current] = reconstruct_path(came_from, current)
                if all([v != None for k, v in stops_paths.items()]):
                    return stops_paths
    
            nodes_currently_discovered.remove(current)
            nodes_already_evaluated.add(current)
    
            for neighbor in vertex_set[current]:
                if neighbor in nodes_already_evaluated:
                    continue
                if neighbor not in nodes_currently_discovered:
                    nodes_currently_discovered.add(neighbor)
                tentative_g_score = g_score[current] + a_star_distance_between(current, neighbor)
                if tentative_g_score >= g_score[neighbor]:
                    continue # not a better path
    
                # best path until now
                came_from[neighbor] = current
                g_score[neighbor] = tentative_g_score
    
                # CHANGE
                f_score[neighbor] = g_score[neighbor] + mean([a_star_heuristic_between(neighbor, stop) for stop in stops])
    

    A *深度锁定

    def a_star(start, stop, max_depth=inf):
            nodes_already_evaluated = set() # a.k.a. closed set 
            nodes_currently_discovered = {start} # a.k.a. open set
    
            came_from = dict()
    
            # for each node, cost of getting from start node to that node
            g_score = {v: inf for v in list(vertex_set.keys())}
            g_score[start] = 0
    
            # for each node, cost of getting from the start node to the goal by passing through that node
            f_score = {v: inf for v in list(vertex_set.keys())}
            f_score[start] = 1 # normally a_star_heuristic_between(start, stop), 1 here because of hard coded value above
    
            # keep track of distance. This is not the most efficient way to do so. However it lets us not have to modify our distance and heuristic functions.
            d_score = {v: inf for v in list(vertex_set.keys())}
            d_score[start] = 0
    
            while nodes_currently_discovered:
                current = a_star_lowest_f_score(f_score, nodes_currently_discovered)
                if current == stop:
                    return reconstruct_path(came_from, current)
    
                nodes_currently_discovered.remove(current)
                nodes_already_evaluated.add(current)
    
                # CHANGE: test for depth
                if d_score[current] + 1 > max_depth:
                    # NOTE: at this point current will NOT be re-evaluated again even if there is a path where getting to current is less than max_depth
                    # this stems from current being placed in nodes_already_evaluated as well as that the node and distance are not kept together in a tuple, e.g. (node, dist) - which requires updating a couple of functions. 
                    continue
    
                for neighbor in vertex_set[current]:
                    if neighbor in nodes_already_evaluated:
                        continue
                    if neighbor not in nodes_currently_discovered:
                        nodes_currently_discovered.add(neighbor)
                        # CHANGE
                        d_score[neighbor] = d_score[current] + 1
    
                    tentative_g_score = g_score[current] + a_star_distance_between(current, neighbor)
                    if tentative_g_score >= g_score[neighbor]:
                        continue # not a better path
    
                    # best path until now
                    came_from[neighbor] = current
                    g_score[neighbor] = tentative_g_score
                    f_score[neighbor] = g_score[neighbor] + a_star_heuristic_between(neighbor, stop)
    

    问题

    我的问题是如何实现A*的版本**(见注释),可以按如下方式调用:

    a_star(start, stops, max_depth, num_paths, max_duration)
    

    **注意:根据所做的更改,结果路径搜索可能不再与A*相比,因为它可能会失去完整性。我使用&#34;版本&#34;松散地简单地表示在这种情况下它基于A*

    其中:

    • start:起始点
    • stops:从头开始查找路径的顶点列表(例如start -> stops[0]start -> stops[1],...,start -> stops[-1]
    • max_depth:一个整数,表示应该探索的最大路径长度,例如max_depth=3开始 - (1) - &gt; a - (2) - &gt; b - (3) - &gt;停止
    • num_paths:每次开始和停止之间要查找的最大路径数,例如如果len(stops)=2那么最多应该有6条路径
    • max_duration:函数终止前允许的最长运行时间并返回到目前为止已找到的内容

    我知道这些变体(不包括最大深度)中的每一个都是与路径查找相关的主要主题。多路径受到各种基础前提的关注。有些方法尝试发送多个代理,例如Bee-Sensor。一种更简单的方法只是从图中删除第一个找到的路径中的那些顶点,然后在子图上重新运行算法(我觉得非常不满意)。如果a --> b --> c --> ea --> b --> d --> e是路径,我希望两者都找到,而不是从评估中丢弃顶点bc

    最近最有趣的贡献之一来自Yin and Yang, 2015。然而,类似于前面提到的删除找到路径的顶点的天真方法,此方法找到具有更大方差的路径(对于它们的用例而言非常好,不适用于我的)。

    我已经实现了我对DFS和BFS的既定目标。然而,a-star的框架让我很难看到如何获得第二,第三等最佳路径。

    当前的想法

    对于多路径,我最初认为存储一个简单的路径列表就足够了,并检查它的长度是否达到了所需的量。但是,这不起作用,因为访问的节点被跟踪的方式分为两组:&#39;关闭&#39; (nodes_already_evaluated)和&#39;打开&#39; (nodes_currently_discovered)设置。如果路径A*返回a --> b --> c,则此时封闭集至少为{a, b, c},我们不希望将b从评估中删除为a -- > b -- d --> c可能是第二个最好的路径。但是,在开放集中包含b和目标(c)只会导致两次相同的路径并重新计算已经确定的值。特别是如果这种多路径方法与多目标混合。

    一个有效的问题可能是&#34;为什么你想要一个多路径,多目标&#34;算法,您可以并行启动多路径单目标搜索。在距离函数恒定或未知的情况下,A*成为贪婪的广度优先搜索算法,因此无论最终目标如何,所探索的边界都是相同的。上面给出的多目标实现只是不断扩展边界,直到达到所有目标,这样更有效。

    我非常感谢为查找路径查找功能提供的任何指导,其中包含先前列出的参数(start,stops,max_depth,num_paths,duration),这些参数至少包含启发式函数并具有竞争性运行时。

    出于测试目的,我提供了以下小图:

    vertex_set = {
        1:  {3, 4, 13, 21},
        2:  {3, 20},
        3:  {1, 2, 4, 5, 23},
        4:  {1, 3, 6},
        5:  {3, 7},
        6:  {4, 8, 16, 23},
        7:  {5, 9},
        8:  {6, 10, 11},
        9:  {7, 12},
        10: {13, 14, 15},
        11: {15, 16},
        12: {9, 20},
        13: {1, 10, 17},
        14: {10, 17, 18},
        15: {10, 11},
        16: {6, 11, 18, 19, 20},
        17: {13, 14},
        18: {14, 16, 21},
        19: {16, 22},
        20: {2, 12, 16},
        21: {1, 18},
        22: {19},
        23: {3, 6, 22}
    }
    

    说明:

    @tobias_k指出了对上述代码的几种优化。例如使用优先级堆来查找最低f分数顶点。

    此外,我想指出上述代码不适用于任何严肃的生产目的。相反,如最初所述,以更易读的语言实现,以帮助讨论如何修改A*算法的核心概念。

    还有其他优化措施。例如,对于深度锁定变体(搜索&lt; =固定长度的路径),它将节省内存和时间以不存储d_score中的所有距离;相反,nodes_already_evaluatednodes_currently_discovered(分别为封闭和开放集合)可以存储(vertex, distance_when_discovered)的元组。这将允许不同深度的两个不同顶点可到达的顶点不止一次地添加到nodes_currently_discovered(开放集) - 这取决于用例,这是假定的期望特征。

    沿着这些方向,@tobias_k建议将顶点的f_score存储在优先级堆的元组中。

    我非常欣赏有关优化的见解。但是,这不是问题的主要目的。

0 个答案:

没有答案