Weighted Graphs

The type of edges of a Graph allows us to defined either Directed or Undirected graphs. However, the edges could also have weights, which represent the cost of moving between the vertices connected by these edges.

exampleaabba--b10cca--c8ddb--d5c--d2

exampleaabba->b2cca->c11

Shortest Path

We cannot use the standard BFS traversal to calculate the shortest path between two vertices. In fact, this implementation simply assumes the same cost for each edge, which is not true for weighted ones.

To overcome this problem, we could use some well-known algorithms.

For all of them, we are using the Network Delay Time problem to apply such algorithms.

Bellman–Ford algorithm

The Bellman–Ford algorithm can be applied to Graphs that have both positive and negative edges. However, it is slower compared to other ones. In fact, the algorithms runs in time.

from collections import deque
from typing import Dict, List, Tuple


class Solution:
    def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
        graph = self.get_adjacency_list(times, n)

        return self.bfs(graph, k)

    def bfs(self, graph: Dict[int, List[Tuple[int, int]]], root: int) -> int:
        queue = deque([root])
        # the first node is `1`, not `0`
        # hence, for simplicity, we create a list of `n + 1` values
        distances = [float("inf")] * (len(graph) + 1)
        distances[root] = 0
        # we have to exclude it once we calculate the maximum below
        distances[0] = float("-inf")

        while queue:
            node = queue.popleft()
            for neighbor, weight in graph[node]:
                new_distance = distances[node] + weight
                if distances[neighbor] <= new_distance:
                    continue

                queue.append(neighbor)
                distances[neighbor] = new_distance

        ans = max(distances)
        return -1 if ans == float("inf") else ans

    def get_adjacency_list(
        self, times: List[List[int]], n: int
    ) -> Dict[int, List[Tuple[int, int]]]:
        graph: Dict[int, List[Tuple[int, int]]] = {i: [] for i in range(1, n + 1)}

        for a, b, weight in times:
            graph[a].append((b, weight))

        return graph

Dijkstra's algorithm

The Dijkstra's algorithm can be applied to Graphs that have only positive edges. However, it is faster compared to the Bellman–Ford algorithm. In fact, the algorithms runs in time.

import heapq
from typing import Dict, List, Tuple


class Solution:
    def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
        graph = self.get_adjacency_list(times, n)

        return self.bfs(graph, k)

    def bfs(self, graph: Dict[int, List[Tuple[int, int]]], root: int) -> int:
        queue = [(0, root)]
        # the first node is `1`, not `0`
        # hence, for simplicity, we create a list of `n + 1` values
        distances = [float("inf")] * (len(graph) + 1)
        distances[root] = 0
        # we have to exclude it once we calculate the maximum below
        distances[0] = float("-inf")

        while queue:
            _, node = heapq.heappop(queue)
            for neighbor, weight in graph[node]:
                new_distance = distances[node] + weight
                if distances[neighbor] <= new_distance:
                    continue

                heapq.heappush(queue, (new_distance, neighbor))
                distances[neighbor] = new_distance

        ans = max(distances)
        return -1 if ans == float("inf") else ans

    def get_adjacency_list(
        self, times: List[List[int]], n: int
    ) -> Dict[int, List[Tuple[int, int]]]:
        graph: Dict[int, List[Tuple[int, int]]] = {i: [] for i in range(1, n + 1)}

        for a, b, weight in times:
            graph[a].append((b, weight))

        return graph