Dijkstra’s Algorithm and Shortest Path
Dijkstra’s algorithm finds the shortest path from the source node to all other nodes where all edge weights are non-negative by using min-heap.
Key idea in this algorithm is to always keep expanding by using the min edge from the set of explored vertices to unexplored vertices. Note that same node could be added into min-heap many times (depending on the implementation) but when it is picked from the min-heap, its shortest distance is final.
Sample problem: https://neetcode.io/problems/network-delay-time
Code:
from heapq import heappush, heappop
class Solution:
def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
minDict = defaultdict(int)
adj = [[] for _ in range(n+1)]
for t in times:
adj[t[0]].append([t[1], t[2]])
# (weight, node)
minHeap = [(0, k)]
while minHeap:
curW, curNode = heappop(minHeap)
if curNode in minDict:
continue
minDict[curNode] = curW
for nei, neiW in adj[curNode]:
if nei not in minDict:
heappush(minHeap, (curW+neiW, nei))
return -1 if len(minDict) != n else max(minDict.values())
Complexity Analysis:
- Runtime Complexity:
O(E x log(E))for the above implementation.- With decrease key:
O(V x log(V) + E x log(V))whereV x log(V)to delete vertices from the heap andE x log(V)to update values in the heap.
- With decrease key:
- Space Complexity:
O(E+V)
Bellman-Ford Algorithm
Bellman-Ford algorithm updates the distances repeatedly with relaxations. Each relaxation tries to improve the shortest distance to a node by expanding the optimal shortest distances.
Code:
class Solution:
def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
dist = [float('inf')] * n
dist[k - 1] = 0
for _ in range(n - 1):
for u, v, w in times:
if dist[u - 1] + w < dist[v - 1]:
dist[v - 1] = dist[u - 1] + w
max_dist = max(dist)
return max_dist if max_dist < float('inf') else -1
Complexity Analysis:
- Runtime Complexity:
O(V x E) - Space Complexity:
O(E+V)
And here is the optimized version of Bellman-Ford algorithm, also known as Shortest Path Faster Algorithm.
- Only process nodes whose distance was actually improved
- Use queue to propagate distance updates
Code:
class Solution:
def networkDelayTime(self, times, n, k):
adj = defaultdict(list)
for u, v, w in times:
adj[u].append((v, w))
dist = {node: float("inf") for node in range(1, n + 1)}
q = deque([(k, 0)])
dist[k] = 0
while q:
node, time = q.popleft()
if dist[node] < time:
continue
for nei, w in adj[node]:
if time + w < dist[nei]:
dist[nei] = time + w
q.append((nei, time + w))
res = max(dist.values())
return res if res < float('inf') else -1
Complexity Analysis:
- Runtime Complexity:
O(V x E)in worst case andO(V+E)in average - Space Complexity:
O(E+V)
Cheapest Flights Within K Stops
Problem: https://neetcode.io/problems/cheapest-flight-path
For this problem, Bellman-Ford algorithm fits very well because of the limit on the path length.
Code:
from collections import deque
class Solution:
def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int:
cost = [float('inf')] * n
cost[src] = 0
for _ in range(k+1):
newCost = cost.copy()
for u, v, price in flights:
if cost[u] != float('inf'):
newCost[v] = min(newCost[v], cost[u] + price)
cost = newCost
return -1 if cost[dst] == float('inf') else cost[dst]