【问题标题】:Dijkstra: how to set a termination condition when finding the destination?Dijkstra:找到目的地时如何设置终止条件?
【发布时间】:2020-09-20 07:49:14
【问题描述】:

众所周知,Dijkstra 会在给定图中找到从单个源节点到任何其他节点的最短路径。我尝试修改原始 Dijkstra 以找到一对源节点和目标节点之间的最短路径。只有在 Dijkstra 找到目标节点时才设置终止程序的终止条件似乎很容易。 但是,我在 Python 代码中设置的“终止条件”似乎导致了次优最短路径,而不是最优最短路径。 Dijkstra代码如下,

def dijkstra(adjList, source, sink):
#define variables
n = len(adjList)    #intentionally 1 more than the number of vertices, keep the 0th entry free for convenience
visited = [False]*n
parent = [-1] *n
#distance = [float('inf')]*n
distance = [1e7]*n
heapNodes = [None]*n
heap = FibonacciHeap()
for i in range(1, n):
    heapNodes[i] = heap.insert(1e7, i)

distance[source] = 0
heap.decrease_key(heapNodes[source], 0)

while heap.total_nodes:
    current = heap.extract_min().value
    #print("Current node is: ", current)
    visited[current] = True
    #early exit
    if sink and current == sink:
        break
    for (neighbor, cost) in adjList[current]:
        if not visited[neighbor]:
            if distance[current] + cost < distance[neighbor]:
                distance[neighbor] = distance[current] + cost
                heap.decrease_key(heapNodes[neighbor], distance[neighbor])
                    if  neighbor == sink and current != source:     # this is a wrong logic , since the neighbor may not be selected as the next hop.
                            print("find the sink 1")
                            printSolution(source, sink, distance,parent)
                            break
    if neighbor == sink:
        print("find the sink2")
        break
return distance

adjList = [
[],
[[2, 7], [3, 9], [6, 14]],
[[1, 7], [4, 15], [3, 10]],
[[1, 9], [2, 10], [4, 11], [6, 2]],
[[2, 15], [3, 11], [5, 6]],
[[4, 6], [6, 9]],
[[5, 9], [1, 14]]
]
dijkstra(adjList,1,4)

邻接表的图形如图所示:

我想找到从节点1到节点4的路径,一共有三个路径:

 path 1: 1 --> 2 --> 4              cost: 22
 path 2: 1 --> 2 --> 3 --> 4        cost: 28  
 path 3: 1 --> 3 --> 4              cost: 20
 path 4: 1 --> 3 --> 6 --> 5 --> 4  cost: 26
 path 5: 1 --> 6 --> 3 --> 4        cost: 28
 path 6: 1 --> 6 --> 5 --> 4        cost: 29

最初,Dijkstra 会选择路径 3:1 --> 3 --> 4,因为它的成本最低。

但是,我修改了终止条件,即当找到当前节点的邻接节点为目的地时,程序将结束。我得到了节点 1 和节点 4 之间路径的结果。结果是路径 1:1 --> 2 --> 4。 我分析了一下,这是因为我设置了错误的终止条件。当找到当前节点的邻接节点是目标时程序将终止,这是错误的,但我不知道在找到目标节点时设置适当的终止条件。请您提供一些想法吗?

【问题讨论】:

    标签: algorithm shortest-path dijkstra


    【解决方案1】:

    终止条件的唯一正确位置是在外循环开始时,您刚刚从堆中获取当前节点。

    在迭代邻居时进行该测试是错误的,因为您无法保证最后一条边是最短路径的一部分。试想一下到邻居的最后一步有一些疯狂的高成本:永远不可能在最短的路径上,所以不要在那里执行终止条件:可能还有另一个更便宜的到 sink 的路径。

    我也没有看到您在代码中实际填充 parent 的位置。

    我也不会从一开始就将所有节点都放在堆上,因为堆在元素较少时会更快。您可以从只有 1 个节点的堆开始。

    另一个小的优化是使用parent 也将节点标记为已访问,因此您实际上不需要parentvisited

    最后,我不知道FibonacciHeap这个库,所以我刚刚取了heapq,这是一个非常轻量级的堆实现:

    from heapq import heappop, heappush
    
    def dijkstra(adjList, source, sink):
        n = len(adjList)
        parent = [None]*n
        heap = [(0, source, 0)] # No need to push all nodes on the heap at the start
        # only add the source to the heap
    
        while heap:
            distance, current, came_from = heappop(heap)
            if parent[current] is not None:  # skip if already visited
                continue
            parent[current] = came_from  # this also marks the node as visited
            if sink and current == sink:  # only correct place to have terminating condition
                # build path
                path = [current]
                while current != source:
                    current = parent[current]
                    path.append(current)
                path.reverse()
                return distance, path
            for (neighbor, cost) in adjList[current]:
                if parent[neighbor] is None:  # not yet visited
                    heappush(heap, (distance + cost, neighbor, current))
    
    adjList = [
    [],
    [[2, 7], [3, 9], [6, 14]],
    [[1, 7], [4, 15], [3, 10]],
    [[1, 9], [2, 10], [4, 11], [6, 2]],
    [[2, 15], [3, 11], [5, 6]],
    [[4, 6], [6, 9]],
    [[5, 9], [1, 14]]
    ]
    dist, path = dijkstra(adjList,1,4)
    print("found shortest path {}, which has a distance of {}".format(path, dist))
    

    【讨论】:

    • 感谢您的回答对我有很大帮助。我执行此代码,它在“dijkstra(adjList,1,4)”的输入下执行得很好,这意味着它可以在正确终止程序的同时找到节点 1 和节点 4 之间的最短路径。但是,当我将输入更改为“dijkstra(adjList,1,5)”时,即找到节点 1 和节点 5 之间的最短路径。该程序给出结果“找到最短路径 [1,3,4,5] ,其距离为 26"。但是最短路径是[1,3,6,5],距离为20。期待你的想法,提前谢谢!
    • 我找到了解决上一篇文章中总结的问题的解决方案。稍微修改@trincot代码, 1.heap = [(source,0,0)] ——> heap = [(0,source,0)] 2. current, distance, come_from = heappop(heap) ——> distance , current, come_from = heappop(heap) 3. heappush(heap, (neighbor, distance + cost, current)) ——> heappush(heap, (distance + cost, neighbor, current)) reseason是heappop应该pop与堆的最小距离。
    • 很高兴您找到了更正!当然,堆应该将距离作为排序键。已更新。
    【解决方案2】:

    您的代码中实际上有正确的退出条件,即 current==sink。您不能强加任何其他退出条件。该算法必须一直运行到目标节点被访问,因为只有在这一点上,您才能确定到目标​​的最短路径的值。由于这种情况,找到单源单目的最短路径的复杂度与单源所有节点最短路径的复杂度相同。所以你的提前退出条件是正确的,你应该删除所有的邻居条件检查。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-05-17
      • 2014-09-23
      • 2022-01-23
      • 2022-12-18
      • 1970-01-01
      • 2012-10-28
      • 1970-01-01
      相关资源
      最近更新 更多