Dijkstra在python中的算法

use*_*080 9 python algorithm dijkstra

我试图使用数组在python中实现Dijkstra的算法.这是我的实施.

def extract(Q, w):
        m=0
        minimum=w[0]
        for i in range(len(w)):
                if w[i]<minimum:
                        minimum=w[i]
                        m=i
        return m, Q[m]
def dijkstra(G, s, t='B'):
   Q=[s]
   p={s:None}
   w=[0]
   d={}
        for i in G:
                d[i]=float('inf')
                Q.append(i)
                w.append(d[i])
        d[s]=0
        S=[]
        n=len(Q)
        while Q:
                u=extract(Q,w)[1]
                S.append(u)
                #w.remove(extract(Q, d, w)[0])
                Q.remove(u)
                for v in G[u]:
                        if d[v]>=d[u]+G[u][v]:
                                d[v]=d[u]+G[u][v]
                                p[v]=u
        return d, p
B='B'
A='A'
D='D'
G='G'
E='E'
C='C'
F='F'
G={B:{A:5, D:1, G:2}, A:{B:5, D:3, E:12, F:5}, D:{B:1, G:1, E:1, A:3}, G:{B:2, D:1, C:2}, C:{G:2, E:1, F:16}, E:{A:12, D:1, C:1, F:2}, F:{A:5, E:2, C:16}}
print "Assuming the start vertex to be B:"
print "Shortest distances", dijkstra(G, B)[0]
print "Parents", dijkstra(G, B)[1]
Run Code Online (Sandbox Code Playgroud)

我希望答案是:

Assuming the start vertex to be B:
Shortest distances {'A': 4, 'C': 4, 'B': 0, 'E': 2, 'D': 1, 'G': 2, 'F': 4}
Parents {'A': 'D', 'C': 'G', 'B': None, 'E': 'D', 'D': 'B', 'G': 'D', 'F': 'E'}
Run Code Online (Sandbox Code Playgroud)

但是,我得到的答案是这样的:

Assuming the start vertex to be B:
Shortest distances {'A': 4, 'C': 4, 'B': 0, 'E': 2, 'D': 1, 'G': 2, 'F': 10}
Parents {'A': 'D', 'C': 'G', 'B': None, 'E': 'D', 'D': 'B', 'G': 'D', 'F': 'A'}.
Run Code Online (Sandbox Code Playgroud)

对于节点F,程序给出错误的答案.有人可以告诉我为什么吗?

Hyp*_*eus 27

正如其他人所指出的那样,由于不使用可理解的变量名,几乎不可能调试代码.

在关于Dijkstra算法的wiki文章之后,可以按照这些方式(以及其他一百万种方式)实现它:

nodes = ('A', 'B', 'C', 'D', 'E', 'F', 'G')
distances = {
    'B': {'A': 5, 'D': 1, 'G': 2},
    'A': {'B': 5, 'D': 3, 'E': 12, 'F' :5},
    'D': {'B': 1, 'G': 1, 'E': 1, 'A': 3},
    'G': {'B': 2, 'D': 1, 'C': 2},
    'C': {'G': 2, 'E': 1, 'F': 16},
    'E': {'A': 12, 'D': 1, 'C': 1, 'F': 2},
    'F': {'A': 5, 'E': 2, 'C': 16}}

unvisited = {node: None for node in nodes} #using None as +inf
visited = {}
current = 'B'
currentDistance = 0
unvisited[current] = currentDistance

while True:
    for neighbour, distance in distances[current].items():
        if neighbour not in unvisited: continue
        newDistance = currentDistance + distance
        if unvisited[neighbour] is None or unvisited[neighbour] > newDistance:
            unvisited[neighbour] = newDistance
    visited[current] = currentDistance
    del unvisited[current]
    if not unvisited: break
    candidates = [node for node in unvisited.items() if node[1]]
    current, currentDistance = sorted(candidates, key = lambda x: x[1])[0]

print(visited)
Run Code Online (Sandbox Code Playgroud)

这段代码比必要的更加冗长,我希望将您的代码与我的代码进行比较,您可能会发现一些差异.

结果是:

{'E': 2, 'D': 1, 'G': 2, 'F': 4, 'A': 4, 'C': 3, 'B': 0}
Run Code Online (Sandbox Code Playgroud)

  • 使用“sorted”确实降低了时间复杂度。您需要一个优先级队列。 (3认同)
  • 当你可以使用 `float('inf')` 时,为什么要使用 `None` 作为 `+inf`? (2认同)
  • 如果您需要获取列表的最小项目,请不要使用排序(复杂度:O(N * log(N)))。使用内置的“min”(复杂度:O(N))。在这里,更喜欢: `min(candidates, key=lambda x: x[1])` 而不是 `sorted(candidates, key = lambda x: x[1])[0]` (2认同)

Enz*_*ama 10

此实现仅使用数组和堆 ds。

import heapq as hq
import math

def dijkstra(G, s):
    n = len(G)
    visited = [False]*n
    weights = [math.inf]*n
    path = [None]*n
    queue = []
    weights[s] = 0
    hq.heappush(queue, (0, s))
    while len(queue) > 0:
        g, u = hq.heappop(queue)
        visited[u] = True
        for v, w in G[u]:
            if not visited[v]:
                f = g + w
                if f < weights[v]:
                    weights[v] = f
                    path[v] = u
                    hq.heappush(queue, (f, v))
    return path, weights

G = [[(1, 6), (3, 7)],
     [(2, 5), (3, 8), (4, -4)],
     [(1, -2), (4, 7)],
     [(2, -3), (4, 9)],
     [(0, 2)]]

print(dijkstra(G, 0))
Run Code Online (Sandbox Code Playgroud)

我希望这可以帮助某人,这有点晚了。

  • 非常简洁的解决方案。但是如何将 Dijkstra 应用于_负_加权图呢? (4认同)
  • @Jianyu Dijkstra 无法处理负值。尝试使用 Bellman-Ford 算法代替 (3认同)

Ion*_*cus 10

我需要一个也能返回路径的解决方案,因此我使用有关 Dijkstra 的多个问题/答案的想法组合了一个简单的类:

class Dijkstra:

    def __init__(self, vertices, graph):
        self.vertices = vertices  # ("A", "B", "C" ...)
        self.graph = graph  # {"A": {"B": 1}, "B": {"A": 3, "C": 5} ...}

    def find_route(self, start, end):
        unvisited = {n: float("inf") for n in self.vertices}
        unvisited[start] = 0  # set start vertex to 0
        visited = {}  # list of all visited nodes
        parents = {}  # predecessors
        while unvisited:
            min_vertex = min(unvisited, key=unvisited.get)  # get smallest distance
            for neighbour, _ in self.graph.get(min_vertex, {}).items():
                if neighbour in visited:
                    continue
                new_distance = unvisited[min_vertex] + self.graph[min_vertex].get(neighbour, float("inf"))
                if new_distance < unvisited[neighbour]:
                    unvisited[neighbour] = new_distance
                    parents[neighbour] = min_vertex
            visited[min_vertex] = unvisited[min_vertex]
            unvisited.pop(min_vertex)
            if min_vertex == end:
                break
        return parents, visited

    @staticmethod
    def generate_path(parents, start, end):
        path = [end]
        while True:
            key = parents[path[0]]
            path.insert(0, key)
            if key == start:
                break
        return path
Run Code Online (Sandbox Code Playgroud)

示例图和用法(绘图是使用这个漂亮的工具生成的): 在此输入图像描述

input_vertices = ("A", "B", "C", "D", "E", "F", "G")
input_graph = {
    "A": {"B": 5, "D": 3, "E": 12, "F": 5},
    "B": {"A": 5, "D": 1, "G": 2},
    "C": {"E": 1, "F": 16, "G": 2},
    "D": {"A": 3, "B": 1, "E": 1, "G": 1},
    "E": {"A": 12, "C": 1, "D": 1, "F": 2},
    "F": {"A": 5, "C": 16, "E": 2},
    "G": {"B": 2, "C": 2, "D": 1}
}
start_vertex = "B"
end_vertex= "C"
dijkstra = Dijkstra(input_vertices, input_graph)
p, v = dijkstra.find_route(start_vertex, end_vertex)
print("Distance from %s to %s is: %.2f" % (start_vertex, end_vertex, v[end_vertex]))
se = dijkstra.generate_path(p, start_vertex, end_vertex)
print("Path from %s to %s is: %s" % (start_vertex, end_vertex, " -> ".join(se)))
Run Code Online (Sandbox Code Playgroud)

输出

Distance from B to C is: 3.00
Path from B to C is: B -> D -> E -> C
Run Code Online (Sandbox Code Playgroud)


Arc*_*ald 9

我用更详细的形式写了它,以使新手读者更清楚:

from collections import defaultdict


def get_shortest_path(weighted_graph, start, end):
    """
    Calculate the shortest path for a directed weighted graph.

    Node can be virtually any hashable datatype.

    :param start: starting node
    :param end: ending node
    :param weighted_graph: {"node1": {"node2": "weight", ...}, ...}
    :return: ["START", ... nodes between ..., "END"] or None, if there is no
            path
    """

    # We always need to visit the start
    nodes_to_visit = {start}
    visited_nodes = set()
    distance_from_start = defaultdict(lambda: float("inf"))
    # Distance from start to start is 0
    distance_from_start[start] = 0
    tentative_parents = {}

    while nodes_to_visit:
        # The next node should be the one with the smallest weight
        current = min(
            [(distance_from_start[node], node) for node in nodes_to_visit]
        )[1]

        # The end was reached
        if current == end:
            break

        nodes_to_visit.discard(current)
        visited_nodes.add(current)

        for neighbour, distance in weighted_graph[current]:
            if neighbour in visited_nodes:
                continue
            neighbour_distance = distance_from_start[current] + distance
            if neighbour_distance < distance_from_start[neighbour]:
                distance_from_start[neighbour] = neighbour_distance
                tentative_parents[neighbour] = current
                nodes_to_visit.add(neighbour)

    return _deconstruct_path(tentative_parents, end)


def _deconstruct_path(tentative_parents, end):
    if end not in tentative_parents:
        return None
    cursor = end
    path = []
    while cursor:
        path.append(cursor)
        cursor = tentative_parents.get(cursor)
    return list(reversed(path))
Run Code Online (Sandbox Code Playgroud)

  • 您可以将 Infinity() 替换为 float('inf') (2认同)
  • Derek,对于 DAG 来说确实如此。在有循环的图中,我们需要防止陷入循环。 (2认同)
  • 这并没有回答原来的问题。而且,为什么不使用 https://docs.python.org/3/library/heapq.html ? (2认同)

小智 8

这不是我的答案 - 我的教授比我的尝试更有效率.这是他的方法,显然使用辅助函数来完成重复性任务

def dijkstra(graph, source):

    vertices, edges = graph
    dist = dict()
    previous = dict()

    for vertex in vertices:
        dist[vertex] = float("inf")
        previous[vertex] = None

    dist[source] = 0
    Q = set(vertices)

    while len(Q) > 0:
        u = minimum_distance(dist, Q)
        print('Currently considering', u, 'with a distance of', dist[u])
        Q.remove(u)

        if dist[u] == float('inf'):
            break

        n = get_neighbours(graph, u)
        for vertex in n:
            alt = dist[u] + dist_between(graph, u, vertex)
            if alt < dist[vertex]:
                dist[vertex] = alt
                previous[vertex] = u

    return previous
Run Code Online (Sandbox Code Playgroud)

给出一个图表

({'A','B','C','D'},{('A','B',5),('B','A',5),('B',' C',10),('B','D',6),('C','D',2),('D','C',2)})

命令print(dijkstra(graph, 'A')产生

目前考虑A距离为0

目前考虑B距离为5

目前考虑D距离为11

目前考虑C距离为13

id est:

{'C':'D','D':'B','A':无,'B':'A'} =>按随机顺序排列

  • 函数“minimum_distance”未定义。 (5认同)

Can*_*vul 5

基于 CLRS 2nd Ed 的实现。第 24.3 章

d是增量,p是前辈

import heapq

def dijkstra(g, s, t):

    q = []
    d = {k: sys.maxint for k in g.keys()}
    p = {}

    d[s] = 0 
    heapq.heappush(q, (0, s))

    while q:
        last_w, curr_v = heapq.heappop(q)
        for n, n_w in g[curr_v]:
            cand_w = last_w + n_w # equivalent to d[curr_v] + n_w 
            # print d # uncomment to see how deltas are updated
            if cand_w < d[n]:
                d[n] = cand_w
                p[n] = curr_v
                heapq.heappush(q, (cand_w, n))

    print "predecessors: ", p 
    print "delta: ", d 
    return d[t]

def test():

    og = {}
    og["s"] = [("t", 10), ("y", 5)]
    og["t"] = [("y", 2), ("x", 1)]
    og["y"] = [("t", 3), ("x", 9), ("z", 2)]
    og["z"] = [("x", 6), ("s", 7)]
    og["x"] = [("z", 4)]

    assert dijkstra(og, "s", "x") == 9 


if __name__ == "__main__":
    test()
Run Code Online (Sandbox Code Playgroud)

实现假设所有节点都表示为键。如果说节点(例如上面示例中的“x”)未定义为og 中的键,则deltas d将缺少该键并检查cand_w < d[n]是否无法正常工作。