25

I am trying to implement Dijkstra's algorithm in python using arrays. This is my implementation.

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]

I expect the answer to be:

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'}

However, the answer that I get is this:

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'}.

For the node F, the program gives the incorrect answer. Can someone please tell me why?

Saurabh
  • 5,176
  • 4
  • 32
  • 46
user3504080
  • 259
  • 1
  • 3
  • 3
  • 21
    Please use meaningful variable names. Will help people understand your code better. – shaktimaan Apr 06 '14 at 17:50
  • 3
    Your code is really confusing: there are 2 different variables named G, unused variable S, and so on. I guess your code just finds ways with no more than 2 edges, as you never add anything to the queue (as you should do in Dijkstra's algorithm), but I can't tell for sure as it is hardly readable. – Natalya Ginzburg Apr 06 '14 at 19:54

13 Answers13

43

As others have pointed out, due to not using understandable variable names, it is almost impossible to debug your code.

Following the wiki article about Dijkstra's algorithm, one can implement it along these lines (and in a million other manners):

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)

This code is more verbous than necessary and I hope comparing your code with mine you might spot some differences.

The result is:

{'E': 2, 'D': 1, 'G': 2, 'F': 4, 'A': 4, 'C': 3, 'B': 0}
Hyperboreus
  • 31,997
  • 9
  • 47
  • 87
  • 2
    why use `None` as `+inf` when you can use `float('inf')`? – fferri Jan 11 '17 at 21:37
  • I see the current/source but where is the destination variable/argument ? – grepit Feb 03 '19 at 01:02
  • 4
    Using `sorted` is really killing the time complexity here. You would need a priority queue. – trincot Jun 27 '20 at 14:19
  • @Hyperboreus How would you choose the next node if the paths have the same length ? – BrockenDuck Mar 12 '21 at 14:01
  • 2
    If you need to get the minimum item of a list don't use a sort (complexity: O(N * log(N))). Use the `min` built-in (complexity: O(N)). Here, prefer: `min(candidates, key=lambda x: x[1])` over `sorted(candidates, key = lambda x: x[1])[0]` – MarAja Dec 16 '21 at 16:31
  • Can Dijkstra's algorithm be easily modified to return *the* shortest path? – jbuddy_13 May 24 '22 at 14:04
  • that is a very pythonic way of doing it. IMO saving the graph as a dict isn't ideal, as you would typically save it as a 2d array/ matrix, especially outside of python. – blkpingu Jun 13 '22 at 14:00
18

This implementation use only array and heap 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))

I hope this could help someone, it's a little bit late.

Enzo Lizama
  • 1,214
  • 1
  • 14
  • 23
  • 4
    Very concise solution. But how can you apply Dijkstra to _negative_ weighted graph? – Jianyu Jul 05 '20 at 20:56
  • 1
    I'm trying to understand this implementation. It seems that the redundant copies produced by hq.heappush(queue, (f, v)) (left there since heappush does not remove the old v with the higher weight) don't matter simply because, by the time v is popped again, all of its neighbors will already have smaller weights, and so the extra copies waste some time but don't alter the results. Is this correct? – Max M Apr 30 '21 at 05:21
  • 3
    @Jianyu Dijkstra cannot handle negative values. Try using the Bellman-Ford algorithm instead – Ben J. Aug 11 '21 at 15:14
  • What are `g` and `u` and `s`? – Nic Scozzaro Jul 18 '22 at 05:07
  • Is the `visited` array necessary? It seems so based on how the priority queue is implemented. We need a way to update existing entries in `hq` without adding new entries for the same vertex. – E. Kaufman Aug 30 '23 at 10:26
  • Perhaps it would be better to turn `visited` into a hash set. Next, turn `while len(queue) > 0` into `while len(visited) < n` and also turn `visited[u] = True` into `visited.add(u)` – E. Kaufman Aug 31 '23 at 06:35
  • I retract the last sentence in my last comment. If the graph is disconnected, the `visited` hash set will never reach size `n` – E. Kaufman Aug 31 '23 at 07:09
12

I wrote it in a more verbose form to make it clearer for a novice reader:

def get_parent(pos):
    return (pos + 1) // 2 - 1


def get_children(pos):
    right = (pos + 1) * 2
    left = right - 1
    return left, right


def swap(array, a, b):
    array[a], array[b] = array[b], array[a]


class Heap:

    def __init__(self):
        self._array = []

    def peek(self):
        return self._array[0] if self._array else None

    def _get_smallest_child(self, parent):
        return min([
            it
            for it in get_children(parent)
            if it < len(self._array)
        ], key=lambda it: self._array[it], default=-1)

    def _sift_down(self):
        parent = 0
        smallest = self._get_smallest_child(parent)
        while smallest != -1 and self._array[smallest] < self._array[parent]:
            swap(self._array, smallest, parent)
            parent, smallest = smallest, self._get_smallest_child(smallest)

    def pop(self):
        if not self._array:
            return None
        swap(self._array, 0, len(self._array) - 1)
        node = self._array.pop()
        self._sift_down()
        return node

    def _sift_up(self):
        index = len(self._array) - 1
        parent = get_parent(index)
        while parent != -1 and self._array[index] < self._array[parent]:
            swap(self._array, index, parent)
            index, parent = parent, get_parent(parent)

    def add(self, item):
        self._array.append(item)
        self._sift_up()

    def __bool__(self):
        return bool(self._array)


def backtrack(best_parents, start, end):
    if end not in best_parents:
        return None
    cursor = end
    path = [cursor]
    while cursor in best_parents:
        cursor = best_parents[cursor]
        path.append(cursor)
        if cursor == start:
            return list(reversed(path))
    return None


def dijkstra(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
    """
    distances = {i: float("inf") for i in weighted_graph}
    best_parents = {i: None for i in weighted_graph}

    to_visit = Heap()
    to_visit.add((0, start))
    distances[start] = 0

    visited = set()

    while to_visit:
        src_distance, source = to_visit.pop()
        if src_distance > distances[source]:
            continue
        if source == end:
            break
        visited.add(source)
        for target, distance in weighted_graph[source].items():
            if target in visited:
                continue
            new_dist = distances[source] + weighted_graph[source][target]
            if distances[target] > new_dist:
                distances[target] = new_dist
                best_parents[target] = source
                to_visit.add((new_dist, target))

    return backtrack(best_parents, start, end)
Archibald
  • 1,305
  • 1
  • 14
  • 19
9

I needed a solution which would also return the path so I put together a simple class using ideas from multiple questions/answers about 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

Example graph and usage (drawing is generated using this nifty tool): enter image description here

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)))

Output

Distance from B to C is: 3.00
Path from B to C is: B -> D -> E -> C
Ionut Ticus
  • 2,683
  • 2
  • 17
  • 25
  • The graph is useful but unfortunately the online tool chooses colors and shapes that are not very pretty. – qwr Oct 07 '21 at 05:23
  • You can edit the vertex style (colors, shapes, text position, size) and edge style (color, line style, width, text) for all items or selected items only (see the `Settings` menu). – Ionut Ticus Oct 07 '21 at 10:26
8

This is not my answer - my prof did it much more efficiently than my attempt. Here is his approach, obviously using helper-functions for the repetitive tasks

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

Given a graph

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

the command print(dijkstra(graph, 'A') yields

Currently considering A with a distance of 0

Currently considering B with a distance of 5

Currently considering D with a distance of 11

Currently considering C with a distance of 13

id est:

{'C': 'D', 'D': 'B', 'A': None, 'B': 'A'} => in random order

fuesika
  • 3,280
  • 6
  • 26
  • 34
docsnuz
  • 97
  • 1
  • 2
7

Implementation based on CLRS 2nd Ed. Chapter 24.3

d is deltas, p is predecessors

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()

Implementation assumes all nodes are represented as keys. If say node(e.g "x" in the example above) was not defined as a key in the og, deltas d would be missing that key and check if cand_w < d[n] wouldn't work correctly.

Can Tuksavul
  • 71
  • 1
  • 2
1

I broke down the wikipedia description into the following pseudo-code on my blog rebrained.com:

Initial state:

  1. give nodes two properties - node.visited and node.distance
  2. set node.distance = infinity for all nodes except set start node to zero
  3. set node.visited = false for all nodes
  4. set current node = start node.

Current node loop:

  1. if current node = end node, finish and return current.distance & path
  2. for all unvisited neighbors, calc their tentative distance (current.distance + edge to neighbor).
  3. if tentative distance < neighbor's set distance, overwrite it.
  4. set current.isvisited = true.
  5. set current = remaining unvisited node with smallest node.distance

http://rebrained.com/?p=392

import sys
def shortestpath(graph,start,end,visited=[],distances={},predecessors={}):
    """Find the shortest path btw start & end nodes in a graph"""
    # detect if first time through, set current distance to zero
    if not visited: distances[start]=0
    # if we've found our end node, find the path to it, and return
    if start==end:
        path=[]
        while end != None:
            path.append(end)
            end=predecessors.get(end,None)
        return distances[start], path[::-1]
    # process neighbors as per algorithm, keep track of predecessors
    for neighbor in graph[start]:
        if neighbor not in visited:
            neighbordist = distances.get(neighbor,sys.maxint)
            tentativedist = distances[start] + graph[start][neighbor]
            if tentativedist < neighbordist:
                distances[neighbor] = tentativedist
                predecessors[neighbor]=start
    # neighbors processed, now mark the current node as visited 
    visited.append(start)
    # finds the closest unvisited node to the start 
    unvisiteds = dict((k, distances.get(k,sys.maxint)) for k in graph if k not in visited)
    closestnode = min(unvisiteds, key=unvisiteds.get)
    # now take the closest node and recurse, making it current 
    return shortestpath(graph,closestnode,end,visited,distances,predecessors)
if __name__ == "__main__":
    graph = {'a': {'w': 14, 'x': 7, 'y': 9},
            'b': {'w': 9, 'z': 6},
            'w': {'a': 14, 'b': 9, 'y': 2},
            'x': {'a': 7, 'y': 10, 'z': 15},
            'y': {'a': 9, 'w': 2, 'x': 10, 'z': 11},
            'z': {'b': 6, 'x': 15, 'y': 11}}
    print shortestpath(graph,'a','a')
    print shortestpath(graph,'a','b')
    """
    Expected Result:
        (0, ['a']) 
        (20, ['a', 'y', 'w', 'b'])
        """
1

This started out as a small rewrite of Carlos' answer, but I ended up changing it quite a lot.

The class its constructer should be passed the graph. This makes it so the get_shortest_path() method can be called many times with different starting and ending arguments, without having to pass the graph every time.

The asserts showcase how the class should be used with a cyclic or acyclic graph.

I personally don't need the weight of the shortest path, so I don't let the get_shortest_path() method return that, but distances[node] can be added to the return statement of the method if it is desired.

import heapq


def main():
    showcase_cyclic()
    showcase_acyclic()


def showcase_cyclic():
    cyclic_graph = {
        "a": { "w": 14, "x": 7, "y": 9 },
        "b": { "w": 9, "z": 6 },
        "w": { "a": 14, "b": 9, "y": 2 },
        "x": { "a": 7, "y": 10, "z": 15 },
        "y": { "a": 9, "w": 2, "x": 10, "z": 11 },
        "z": { "b": 6, "x": 15, "y": 11 }
    }

    dijkstra = Dijkstra(cyclic_graph)

    assert dijkstra.get_shortest_path("a", "a") == ['a']
    assert dijkstra.get_shortest_path("a", "b") == ['a', 'y', 'w', 'b']


def showcase_acyclic():
    acyclic_graph = {
        "a": { "b": 1, "c": 2 },
        "b": { "d": 3 },
        "c": { "d": 4 },
        "d": { "e": 5 },
        "e": {}
    }

    dijkstra = Dijkstra(acyclic_graph)

    assert dijkstra.get_shortest_path("a", "a") == ['a']
    assert dijkstra.get_shortest_path("a", "e") == ['a', 'b', 'd', 'e']


class Dijkstra:
    def __init__(self, graph):
        self.graph = graph


    def get_shortest_path(self, start, end):
        distances = { key: float("inf") for key in self.graph.keys() }
        distances[start] = 0

        unvisited = { key for key in self.graph.keys() }

        parents = {}

        node = start

        while node != end:
            unvisited.remove(node)

            for neighbor in self.graph[node].keys():
                if neighbor in unvisited:
                    tentative_distance = distances[node] + self.graph[node][neighbor]
                    recorded_distance = distances[neighbor]

                    if tentative_distance < recorded_distance:
                        distances[neighbor] = tentative_distance
                        parents[neighbor] = node

            node = self.get_closest_node(unvisited, distances)

        return self.get_path(end, parents)


    def get_closest_node(self, unvisited, distances):
        unvisited_min_heap = [(distances[node], node) for node in unvisited]

        # Great explanation of heapify: https://stackoverflow.com/a/61446534/13279557
        heapq.heapify(unvisited_min_heap)

        _, closest_node = heapq.heappop(unvisited_min_heap)

        return closest_node


    def get_path(self, end, parents):
        path = []

        while True:
            path.append(end)

            if end not in parents:
                return list(reversed(path))

            end = parents[end]


if __name__ == "__main__":
    main()
MyNameIsTrez
  • 117
  • 2
  • 13
1

Using heapq module to implement priority queue, you can do:

from collections import defaultdict
import heapq

graph = {
    'A': [('B', 2), ('C', 1)],
    'B': [('A', 2), ('C', 4), ('D', 3)],
    'C': [('A', 1), ('B', 4), ('E', 2)],
    'E': [('C', 2), ('D', 1), ('F', 4)],
    'D': [('B', 3), ('E', 1), ('F', 2)],
    'F': [('D', 2), ('E', 4)]

}


def dijkstra(graph, start: str):
    result_map = defaultdict(lambda: float('inf'))
    result_map[start] = 0

    visited = set()

    queue = [(0, start)]

    while queue:
        weight, v = heapq.heappop(queue)
        visited.add(v)

        for u, w in graph[v]:
            if u not in visited:
                result_map[u] = min(w + weight, result_map[u])
                heapq.heappush(queue, [w + weight, u])

    return result_map


print(dijkstra(graph, 'A'))

Output:

{'A': 0, 'B': 2, 'C': 1, 'E': 3, 'D': 4, 'F': 6}
funnydman
  • 9,083
  • 4
  • 40
  • 55
0

Set a breakpoint in extract. You will see that you delete entries from Q but never from w. Everything else is a dict, but Q/w are a paired array which you do not keep up to date. You have to keep those two in sync, or replace them with a dict. Special note: Eventually, after the algorithm is working, you may want to replace Q/w with a list of edges and re-code the "extract" function with a priority queue (heapq module).

Additionally, you will see that w always has weights of 0 for the source, and 'inf' for all other nodes. You completely skipped the critical step where you update the candidate distances.

So you are basically always taking the first path you encounter, rather than selecting the shortest path. You later compute the actual distance of that path, so the returned array of distances has actual values, but they were chosen arbitrarily, and you have no reason to expect them to be shortest.

After you (incorrectly) find the next node, you look at all of its edges. This should have been the critical step I mentioned above in the second paragraph, where you update the candidates for the next node. Instead you do something completely different: You appear to be looping through all the previous solutions (which are guaranteed correct and should be left alone, if you correctly implement dijkstra), and you look for a two-step solution from source->current->any. The correct intent for looking at those would have been to add the next candidate from previous paths to the next node, but because they never get added, you don't look at (previous shortest path) + (one step), you only look at literally two node solutions.

So basically, you are looping through all possible two-node paths from source in order to find the shortest paths. This is a complete error and has nothing to do with dijkstra. But it almost works on your tiny tiny graph where most of the correct shortest paths are two-step paths.

(ps: I agree with everyone about your variable names. You would have done much better if you use verbose names telling what those variables represent. I had to rename them before I got anywhere analyzing your code.)

Kenny Ostrom
  • 5,639
  • 2
  • 21
  • 30
0
import sys
import heapq

class Node:

     def __init__(self, name):
        self.name = name
        self.visited = False
        self.adjacenciesList = []
        self.predecessor = None
        self.mindistance = sys.maxsize    

    def __lt__(self, other):
        return self.mindistance < other.mindistance

class Edge:

    def __init__(self, weight, startvertex, endvertex):
        self.weight = weight
        self.startvertex = startvertex
        self.endvertex = endvertex

def calculateshortestpath(vertexlist, startvertex):
    q = []
    startvertex.mindistance = 0
    heapq.heappush(q, startvertex)

    while q:
        actualnode = heapq.heappop(q)
        for edge in actualnode.adjacenciesList:
            tempdist = edge.startvertex.mindistance + edge.weight
            if tempdist < edge.endvertex.mindistance:
                edge.endvertex.mindistance = tempdist
                edge.endvertex.predecessor = edge.startvertex
                heapq.heappush(q,edge.endvertex)
def getshortestpath(targetvertex):
    print("The value of it's minimum distance is: ",targetvertex.mindistance)
    node = targetvertex
    while node:
        print(node.name)
        node = node.predecessor

node1 = Node("A");
node2 = Node("B");
node3 = Node("C");
node4 = Node("D");
node5 = Node("E");
node6 = Node("F");
node7 = Node("G");
node8 = Node("H");

edge1 = Edge(5,node1,node2);
edge2 = Edge(8,node1,node8);
edge3 = Edge(9,node1,node5);
edge4 = Edge(15,node2,node4);
edge5 = Edge(12,node2,node3);
edge6 = Edge(4,node2,node8);
edge7 = Edge(7,node8,node3);
edge8 = Edge(6,node8,node6);
edge9 = Edge(5,node5,node8);
edge10 = Edge(4,node5,node6);
edge11 = Edge(20,node5,node7);
edge12 = Edge(1,node6,node3);
edge13 = Edge(13,node6,node7);
edge14 = Edge(3,node3,node4);
edge15 = Edge(11,node3,node7);
edge16 = Edge(9,node4,node7);

node1.adjacenciesList.append(edge1);
node1.adjacenciesList.append(edge2);
node1.adjacenciesList.append(edge3);
node2.adjacenciesList.append(edge4);
node2.adjacenciesList.append(edge5);
node2.adjacenciesList.append(edge6);
node8.adjacenciesList.append(edge7);
node8.adjacenciesList.append(edge8);
node5.adjacenciesList.append(edge9);
node5.adjacenciesList.append(edge10);
node5.adjacenciesList.append(edge11);
node6.adjacenciesList.append(edge12);
node6.adjacenciesList.append(edge13);
node3.adjacenciesList.append(edge14);
node3.adjacenciesList.append(edge15);
node4.adjacenciesList.append(edge16);

vertexlist = (node1,node2,node3,node4,node5,node6,node7,node8)

calculateshortestpath(vertexlist,node1)
getshortestpath(node7)
  • 1
    Code-only answers are not considered good practice. Please add a brief explanation of how your code addresses the question. (read docs on how to ask a question in SO) – Yannis Dec 19 '17 at 12:56
0

Here is my implementation of Dijkstra algorithm using min-priority-queue. Hope it will you.

from collections import defaultdict
from math import floor


class MinPQ:
    """
    each heap element is in form (key value, object handle), while heap
    operations works based on comparing key value and object handle points to
    the corresponding application object.
    """

    def __init__(self, array=[]):
        self._minheap = list(array)
        self._length = len(array)
        self._heapsize = 0
        self._build_min_heap()

    def _left(self, idx):
        return 2*idx+1

    def _right(self, idx):
        return 2*idx+2

    def _parent(self, idx):
        return floor((idx-1)/2)

    def _min_heapify(self, idx):
        left = self._left(idx)
        right = self._right(idx)
        min_idx = idx
        if left <= self._heapsize-1 and self._minheap[left] < self._minheap[min_idx]:
            min_idx = left
        if right <= self._heapsize-1 and self._minheap[right] < self._minheap[min_idx]:
            min_idx = right
        if min_idx != idx:
            self._minheap[idx], self._minheap[min_idx] = self._minheap[min_idx], self._minheap[idx]
            self._min_heapify(min_idx)

    def _build_min_heap(self):
        self._heapsize = self._length
        mid_id = int(self._heapsize-1)-1
        for i in range(mid_id, -1, -1):
            self._min_heapify(i)

    def decrease_key(self, idx, new_key):
        while idx > 0 and new_key < self._minheap[self._parent(idx)]:
            self._minheap[idx] = self._minheap[self._parent(idx)]
            idx = self._parent(idx)
        self._minheap[idx] = new_key

    def extract_min(self):
        minimum = self._minheap[0]
        self._minheap[0] = self._minheap[self._heapsize-1]
        self._heapsize = self._heapsize - 1
        self._min_heapify(0)
        return minimum

    def insert(self, item):
        self._minheap.append(item)
        self._heapsize = self._heapsize + 1
        self.decrease_key(self._heapsize-1, item)

    @property
    def minimum(self):
        return self._minheap[0]

    def is_empty(self):
        return self._heapsize == 0

    def __str__(self):
        return str(self._minheap)

    __repr__ = __str__

    def __len__(self):
        return self._heapsize


class DiGraph:
    def __init__(self, edges=None):
        self.adj_list = defaultdict(list)
        self.add_weighted_edges(edges)

    @property
    def nodes(self):
        nodes = set()
        nodes.update(self.adj_list.keys())
        for node in self.adj_list.keys():
            for neighbor, weight in self.adj_list[node]:
                nodes.add(neighbor)
        return list(nodes)

    def add_weighted_edges(self, edges):
        if edges is None:
            return None
        for edge in edges:
            self.add_weighted_edge(edge)

    def add_weighted_edge(self, edge):
        node1, node2, weight = edge
        self.adj_list[node1].append((node2, weight))

    def weight(self, tail, head):
        for node, weight in self.adj_list[tail]:
            if node == head:
                return weight
        return None


def relax(min_heapq, dist, graph, u, v):
    if dist[v] > dist[u] + graph.weight(u, v):
        dist[v] = dist[u] + graph.weight(u, v)
        min_heapq.insert((dist[v], v))


def dijkstra(graph, start):
    # initialize
    dist = dict.fromkeys(graph.nodes, float('inf'))
    dist[start] = 0
    min_heapq = MinPQ()
    min_heapq.insert((0, start))

    while not min_heapq.is_empty():
        distance, u = min_heapq.extract_min()
        # we may add a node multiple time in priority queue, but we process it
        # only once
        if distance > dist[u]:
            continue
        for neighbor, weight in graph.adj_list[u]:
            relax(min_heapq, dist, graph, u, neighbor)

    return dist

if __name__ == "__main__":
    edges = [('s', 't', 10), ('t', 'x', 1), ('s', 'y', 5), ('y', 't', 3), ('t', 'y', 2), 
             ('y', 'x', 9), ('y', 'z', 2), ('z', 's', 7), ('x', 'z', 4), ('z', 'x', 6)]
    digraph = DiGraph(edges)
    res = dijkstra(digraph, 's')
    print(res)

Yossarian42
  • 1,950
  • 17
  • 14
0

I implement Dijkstra using priority-queue. Apart from that, I also implement min-heap myself. Hope this will help you.

from collections import defaultdict


class MinPQ:
    """
    each heap element is in form (key value, object handle), while heap
    operations works based on comparing key value and object handle points to
    the corresponding application object.
    """

    def __init__(self, array=[]):
        self._minheap = list(array)
        self._length = len(array)
        self._heapsize = 0
        self._build_min_heap()

    def _left(self, idx):
        return 2*idx+1

    def _right(self, idx):
        return 2*idx+2

    def _parent(self, idx):
        return int((idx-1)/2)

    def _min_heapify(self, idx):
        left = self._left(idx)
        right = self._right(idx)
        min_idx = idx
        if left <= self._heapsize-1 and self._minheap[left] < self._minheap[min_idx]:
            min_idx = left
        if right <= self._heapsize-1 and self._minheap[right] < self._minheap[min_idx]:
            min_idx = right
        if min_idx != idx:
            self._minheap[idx], self._minheap[min_idx] = self._minheap[min_idx], self._minheap[idx]
            self._min_heapify(min_idx)

    def _build_min_heap(self):
        self._heapsize = self._length
        mid_id = int((self._heapsize)/2)-1
        for i in range(mid_id, -1, -1):
            self._min_heapify(i)

    def decrease_key(self, idx, new_key):
        while idx > 0 and new_key < self._minheap[self._parent(idx)]:
            self._minheap[idx] = self._minheap[self._parent(idx)]
            idx = self._parent(idx)
        self._minheap[idx] = new_key

    def extract_min(self):
        if self._heapsize < 1:
            raise IndexError
        minimum = self._minheap[0]
        self._minheap[0] = self._minheap[self._heapsize-1]
        self._heapsize = self._heapsize - 1
        self._min_heapify(0)
        return minimum

    def insert(self, item):
        self._minheap.append(item)
        self._heapsize = self._heapsize + 1
        self.decrease_key(self._heapsize-1, item)

    @property
    def minimum(self):
        return self._minheap[0]

    def is_empty(self):
        return self._heapsize == 0

    def __str__(self):
        return str(self._minheap)

    __repr__ = __str__

    def __len__(self):
        return self._heapsize


class DiGraph:
    def __init__(self, edges=None):
        self.adj_list = defaultdict(list)
        self.add_weighted_edges(edges)

    @property
    def nodes(self):
        nodes = set()
        nodes.update(self.adj_list.keys())
        for node in self.adj_list.keys():
            for neighbor, weight in self.adj_list[node]:
                nodes.add(neighbor)
        return list(nodes)

    def add_weighted_edges(self, edges):
        if edges is None:
            return None
        for edge in edges:
            self.add_weighted_edge(edge)

    def add_weighted_edge(self, edge):
        node1, node2, weight = edge
        self.adj_list[node1].append((node2, weight))

    def weight(self, tail, head):
        for node, weight in self.adj_list[tail]:
            if node == head:
                return weight
        return None


def relax(min_heapq, dist, graph, u, v):
    if dist[v] > dist[u] + graph.weight(u, v):
        dist[v] = dist[u] + graph.weight(u, v)
        min_heapq.insert((dist[v], v))


def dijkstra(graph, start):
    # initialize
    dist = dict.fromkeys(graph.nodes, float('inf'))
    dist[start] = 0
    min_heapq = MinPQ()
    min_heapq.insert((0, start))

    while not min_heapq.is_empty():
        distance, u = min_heapq.extract_min()
        # we may add a node multiple time in priority queue, but we process it
        # only once
        if distance > dist[u]:
            continue
        for neighbor, weight in graph.adj_list[u]:
            relax(min_heapq, dist, graph, u, neighbor)

    return dist
Yossarian42
  • 1,950
  • 17
  • 14