Dijkstra's algorithm in python

后端 未结 11 1426
无人及你
无人及你 2021-02-01 09:32

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


        
相关标签:
11条回答
  • 2021-02-01 09:34
    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)
    
    0 讨论(0)
  • 2021-02-01 09:35

    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)
    
    
    0 讨论(0)
  • 2021-02-01 09:37

    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

    0 讨论(0)
  • 2021-02-01 09:38

    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.

    0 讨论(0)
  • 2021-02-01 09:38

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

    0 讨论(0)
  • 2021-02-01 09:42

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

    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
    
    0 讨论(0)
提交回复
热议问题