日元的算法实现未选择最短路径

时间:2018-06-20 22:55:08

标签: python search dijkstra shortest-path

我正在使用日元算法(Wikipedia)查找图中的k条最短路径。在下面的示例中,我的图是一个字典,其中每个节点都是键,其值是邻居。 Map()中的dotmap仅允许将字典转换为可以使用点表示法访问键的对象。我想找到从A到F降序排列的四个最短路径,其中每个边缘的权重相等。前两个是联系(A> B> D> F)和(A> E> D> F),后两个是(A> B> C> G> F),最后是(A> B> D> C> G> F)。我的Dijkstra的实现(尽管没有启发式方法也被称为AStar)可能存在缺陷,因为它在找不到路径时会返回一个空列表。如何让我的代码仅选择有效路径?当前它返回[['A', 'B', 'D', 'F'], ['A', 'E', 'D', 'F'], [], []] -应该返回最短路径[['A', 'B', 'D', 'F'], ['A', 'E', 'D', 'F'], ['A', 'B', 'C', 'G', 'F'], ['A', 'B', 'D', 'C', 'G', 'F']]

import copy
import heapq
from dotmap import Map
from itertools import count

graph = {
    'A': ['B', 'E'],
    'B': ['C', 'D'],
    'C': ['G'],
    'D': ['C', 'F'],
    'E': ['D'],
    'F': [],
    'G': ['F']
}

class PriorityQueue:
    def __init__(self):
        self.elements = []
        self._counter = count()

    def empty(self):
        return len(self.elements) == 0

    def put(self, item, priority):
        heapq.heappush(self.elements, (priority, item,))

    def get(self):
        return heapq.heappop(self.elements)[1]


class AStar:
    def __init__(self, graph, start, goals=[]):
        self.graph = graph
        self.start = start
        self.frontier = PriorityQueue()
        self.frontier.put(start, 0)
        self.previous = {}
        self.previous[start] = None
        self.costs = {}
        self.costs[start] = 0
        self.final_path = None
        self.goals = goals
        self.goal = None

    def search(self):
        graph = self.graph
        frontier = self.frontier
        goals = self.goals
        costs = self.costs
        while not frontier.empty():
            state = frontier.get()
            if state in goals:
                cost = self.costs[state]
                self.goal = state
                self.final_path = self.trace_path()
                return Map({'path': self.final_path, 'cost': cost})

            for next_state in graph[state]:
                new_cost = costs[state] + 1
                if next_state not in costs or new_cost < costs[next_state]:
                    costs[next_state] = new_cost
                    priority = new_cost
                    frontier.put(next_state, priority)
                    self.previous[next_state] = state
        # No path found
        return Map({'path': [], 'cost': 0})

    def trace_path(self):
        current = self.goal
        path = []
        while current != self.start:
            path.append(current)
            current = self.previous[current]
        path.append(self.start)
        path.reverse()
        return path

def YenKSP(graph, source, sink, k_paths):   
    graph_clone = copy.deepcopy(graph)
    A = [AStar(graph, source, sink).search().path]
    B = []

    for k in range(1, k_paths):
        for i in range(len(A[-1]) - 1):
            spur_node = A[-1][i]
            root_path = A[-1][:i+1]
            for path in A:
                if len(path) > i and root_path == path[:i+1]:
                    graph_clone[path[i]].remove(path[i+1])

            result = AStar(graph_clone, spur_node, sink).search()
            spur_path = result.path
            total_path = root_path[:-1] + spur_path
            spur_cost = AStar(graph_clone, source, spur_node).search().cost
            B.append(Map({'path': total_path, 'cost': result.cost + spur_cost}))
            graph_clone = copy.deepcopy(graph)
        if len(B) == 0:
            break
        B.sort(key=lambda p: (p.cost, len(p.path)))
        A.append(B[0].path)
        B.pop()
    return A

paths = YenKSP(graph, 'A', 'F', 4)
print(paths)

1 个答案:

答案 0 :(得分:1)

import copy
import heapq
#from dotmap import Map
from itertools import count

class Map(dict):
    def __getattr__(self, k):
        return self[k]
    def __setattr__(self, k, v):
        self[k] = v

graph = {
    'A': ['B', 'E'],
    'B': ['C', 'D'],
    'C': ['G'],
    'D': ['C', 'F'],
    'E': ['D'],
    'F': [],
    'G': ['F']
}

class PriorityQueue:
    def __init__(self):
        self.elements = []
        self._counter = count()

    def empty(self):
        return len(self.elements) == 0

    def put(self, item, priority):
        heapq.heappush(self.elements, (priority, item,))

    def get(self):
        return heapq.heappop(self.elements)[1]


class AStar:
    def __init__(self, graph, start, goals=[]):
        self.graph = graph
        self.start = start
        self.frontier = PriorityQueue()
        self.frontier.put(start, 0)
        self.previous = {}
        self.previous[start] = None
        self.costs = {}
        self.costs[start] = 0
        self.final_path = None
        self.goals = goals
        self.goal = None

    def search(self):
        graph = self.graph
        frontier = self.frontier
        goals = self.goals
        costs = self.costs
        while not frontier.empty():
            state = frontier.get()
            if state in goals:
                cost = self.costs[state]
                self.goal = state
                self.final_path = self.trace_path()
                return Map({'path': self.final_path, 'cost': cost})

            for next_state in graph[state]:
                new_cost = costs[state] + 1
                if next_state not in costs or new_cost < costs[next_state]:
                    costs[next_state] = new_cost
                    priority = new_cost
                    frontier.put(next_state, priority)
                    self.previous[next_state] = state
        # No path found
        return Map({'path': [], 'cost': float('inf')})

    def trace_path(self):
        current = self.goal
        path = []
        while current != self.start:
            path.append(current)
            current = self.previous[current]
        path.append(self.start)
        path.reverse()
        return path

def YenKSP(graph, source, sink, k_paths):
    A = [AStar(graph, source, sink).search().path]
    B = []

    for _ in range(1, k_paths):
        for i in range(len(A[-1]) - 1):
            graph_clone = copy.deepcopy(graph)

            spur_node = A[-1][i]
            root_path = A[-1][:i+1]
            for path in A:
                if len(path) > i and root_path == path[:i+1]:
                    if path[i+1] in graph_clone[path[i]]:
                        graph_clone[path[i]].remove(path[i+1])

            result = AStar(graph_clone, spur_node, sink).search()
            spur_path = result.path
            total_path = root_path[:-1] + spur_path
            spur_cost = AStar(graph_clone, source, spur_node).search().cost
            B.append(Map({'path': total_path, 'cost': result.cost + spur_cost}))

        if len(B) == 0:
            break

        B.sort(key=lambda p: (p.cost, len(p.path)))
        best_b = B.pop(0)
        if best_b.cost != float('inf'):
            A.append(best_b.path)
    return A

paths = YenKSP(graph, 'A', 'F', 4)
print(paths)

产生:

[['A', 'B', 'D', 'F'], ['A', 'E', 'D', 'F'], ['A', 'B', 'C', 'G', 'F'], ['A', 'B', 'D', 'C', 'G', 'F']]

主要问题是,当没有找到路径时,默认值将返回成本为0的路径。因此,按路径成本排序时,这些路径在B中显示为最佳选择,并已添加到A中。我将默认路径成本更改为float('inf')。这样做揭示了一个错误,当您尝试从graph_clone(在for path in A: ...内部)删除同一条边两次时,可能会发生此错误,因此我添加了if检查以有条件地删除该边。 the diff的最后两件事表明我确实是(a)模仿您的dotmap.Map类(可以删除该类并取消注释导入),并且(b)仅向结果集{{1 }}如果费用有限。