Reputation: 19
Trying to write dijkstras in python. How do I implement the decrease key operation in O(logn) when I can't modify my tuples?
I'm writing a snippet to solve dijkstras on an adjacency list and require a priority queue implementation. I'm currently passing a tuple of (priority, value) to the pq so heapify handles the push and pop for me. However, I can't modify tuples so I can't efficiently decrease the priority of any item and have to pop all items till my required one is found, then readd all items to the pq, which makes the time complexity O(V^2). Is there any way to modify this code so the decreaseKey operation works in O(logn) time? Preferably no classes involved. I can't use lists instead of tuples; it wouldn't heapify otherwise
def decrease_key(pq, v, val):
li = []
u = heapq.heappop(pq)
while u[1] is not v:
li.append(u)
u = heapq.heappop(pq)
for l in li:
heapq.heappush(pq, l)
heapq.heappush(pq, (val, v))
def dijkstra(G, src, dist, V):
for v in range(V):
dist[v] = float('inf')
dist[src] = 0
pq = []
for k, v in dist.items():
pq.append((v, k))
while pq:
u = heapq.heappop(pq)[1]
for v, w in G[u]:
if dist[u] + w < dist[v]:
dist[v] = dist[u] + w
decrease_key(pq, v, dist[v])
O(n^2) vs required O(nlogn)
Upvotes: 0
Views: 714
Reputation: 41
It's been a while but I think the point of the heap is to keep a queue of the minimum distance nodes to recalculate and update your distances.
import heapq
def dijkstra(graph, src):
# Read all graph nodes
nodes = list(graph.keys())
# Initialize all distances to infinity and build a queue of unvisted nodes
dist = {}
pq = []
for node in nodes:
dist[node] = float('inf')
dist[src] = 0
heapq.heappush(pq, (0, src))
# While shorter distances to nodes, recalculate neighbor distances
while pq:
src_dist, unvisited_node = heapq.heappop(pq)
# Recalculate total distance for each neighbor
unvisted_neighbors = graph.get(unvisited_node, [])
for n_node, n_dist in unvisted_neighbors:
test_dist = src_dist + n_dist
# If test distance is shorted, update and enqueue
if test_dist < dist[n_node]:
dist[n_node] = test_dist
heapq.heappush(pq, (test_dist, n_node))
return dist
test_graph = {
'a': (('b', 7), ('c', 9), ('f', 14)),
'b': (('a', 7), ('d', 15), ('c', 10)),
'c': (('a', 9), ('b', 10), ('d', 11), ('f', 2)),
'd': (('b', 15), ('c', 11), ('e', 6)),
'e': (('d', 6), ('f', 9)),
'f': (('c', 2), ('e', 9))
}
'''Lazy graph structure.
key: source node name
value: adjacent node name and distance pairs
https://www.bogotobogo.com/python/images/Graph/graph_diagram.png
'''
src_node = 'e'
d = dijkstra(test_graph, src_node)
for k, v in d.items():
print(f'{src_node}->{k}: {v}')
Upvotes: 0
Reputation: 59293
As @DavidEisenstat indicates in comments, you don't need decrease_key if you just add multiple records to the heap.
You also need to keep track of which nodes you've popped off the priority queue, so you can only process a node the first time you see it. (Otherwise the worst case complexity increases)
And finally, it's nice if you avoid pushing those infinite costs into the heap, and if you only push a new node when you actually lower the cost. All together, something like this:
def dijkstra(G, src, dist, V):
for v in range(V):
dist[v] = float('inf')
dist[src] = 0
pq = [(0,src)]
seen = [False]*V
while pq:
u = heapq.heappop(pq)[1]
if seen[u]:
continue
seen[u] = True
for v, w in G[u]:
if dist[u] + w < dist[v]:
dist[v] = dist[u] + w
heapq.heappush( pq, (dist[v],v) )
Upvotes: 2