Bellman-Ford

This tutorial is a part of the Data Structures and Algorithms class:

Typical problems solved by Bellman-Ford algorithm

  • Single source shortest/longest path: find shortest/longest path from a source node to all other nodes in the graph. Simple, Mathematically proven.
  • I. When to use: shortest/longest path, also works with negative edges.
  • II. Conditions to use: shortest path: NO Negative cycle, longest path: No Positive cycle.
  • III. How to use: get the edges, then relax them v-1 times (v is the number of vertices).
  • Relaxation shortest path: if (d[u] + c(u,v) < d[v]) then d[v] = d[u] + c(u,v)
  • Relaxation longest path: if (d[u] + c(u,v) > d[v]) then d[v] = d[u] + c(u,v)

bellman ford shortest path

lecture-29/shortest-path-bellman-ford.js
// Shortest path, Bellman-Ford

// NO NEGATIVE weight cycle in graph is a MUST
const graph = [
  [[1, 4], [3, 5]], // node 0, index 0
  [[4, 7]], // node 1, index 1
  [[1, 3]], // node 2, index 2
  [[2, -10]], // node 3, index 3
  [], // node 4, index 4
]

// graph contains NEGATIVE weight cycle
const graphWrong = [
  [[1, 4], [3, 5]], // node 0, index 0
  [[4, 7], [3, 6]], // node 1, index 1
  [[1, 3]], // node 2, index 2
  [[2, -10]], // node 3, index 3
  [], // node 4, index 4
]


const shortestPathBellmanFord = (graph, sourceIdx) => {

  // STEP 1: transform graph from adjacency list to Vertices + Edges lists
  const vertices = []
  const edges = []

  for (let i = 0; i < graph.length; i++) {

    // list of node indices
    vertices.push(i)
    
    // list of edges [from node, to node, weight]
    for (let j = 0; j < graph[i].length; j++) {
      const [toNode, weight] = graph[i][j]
      edges.push([i, toNode, weight])
    }
  }

  console.log('vertices:', vertices)
  console.log('edges:', edges)

  // STEP 2: build initial result map
  const result = {}
  for (let i = 0; i < vertices.length; i++) {
    if(vertices[i] == sourceIdx) {
      result[i] = 0
      continue
    }
    result[i] = Infinity
  }

  console.log('result:', result)

  // STEP 3: relax the edges n-1 times
  const relaxTimes = vertices.length - 1

  for (let i = 0; i < relaxTimes; i++) {
    for (let j = 0; j < edges.length; j++) {
      const [fromNode, toNode, weight] = edges[j]
      const newVal = Math.min(result[fromNode] + weight, result[toNode])

      console.log(`relax (${fromNode},${toNode}), val old: ${result[toNode]}, new: ${newVal}`)

      result[toNode] = newVal
    }
  }

  return JSON.stringify(result, null, 4)
}

const sourceIdx = 0

console.log(`Shortest path Bellman-Ford from node ${sourceIdx}:`, shortestPathBellmanFord(graph, sourceIdx))
// console.log(`Shortest path graph has NEGATIVE cycle ${sourceIdx}:`, shortestPathBellmanFord(graphWrong, sourceIdx))
$ node shortest-path-bellman-ford.js
vertices: [ 0, 1, 2, 3, 4 ]
edges: [ [ 0, 1, 4 ], [ 0, 3, 5 ], [ 1, 4, 7 ], [ 2, 1, 3 ], [ 3, 2, -10 ] ]
result: { '0': 0, '1': Infinity, '2': Infinity, '3': Infinity, '4': Infinity }
...
Shortest path Bellman-Ford from node 0: {
    "0": 0,
    "1": -2,
    "2": -5,
    "3": 5,
    "4": 5
}

Longest path with Bellman-Ford algorithm

bellman ford longest path

lecture-29/longest-path-bellman-ford.js
// Longest path, Bellman-Ford

// NO POSITIVE weight cycle in graph is a MUST
const graph = [
  [[1, 4], [3, 5]], // node 0, index 0
  [[4, 7]], // node 1, index 1
  [[1, 3]], // node 2, index 2
  [[2, -10]], // node 3, index 3
  [], // node 4, index 4
]

// graph contains POSITIVE weight cycle
const graphWrong = [
  [[1, 4], [3, 5]], // node 0, index 0
  [[4, 7]], // node 1, index 1
  [[1, 3]], // node 2, index 2
  [[2, -10]], // node 3, index 3
  [[2, 1]], // node 4, index 4
]

const sourceIdx = 0

const longestPathBellmanFord = (graph, sourceIdx) => {

  // STEP 1: transform graph from adjacency list to Vertices + Edges lists
  const vertices = []
  const edges = []

  for (let i = 0; i < graph.length; i++) {

    // list of node indices
    vertices.push(i)
    
    // list of edges [from node, to node, weight]
    for (let j = 0; j < graph[i].length; j++) {
      const [toNode, weight] = graph[i][j]
      edges.push([i, toNode, weight])
    }
  }

  console.log('vertices:', vertices)
  console.log('edges:', edges)

  // STEP 2: build initial result map
  const result = {}
  for (let i = 0; i < vertices.length; i++) {
    if(vertices[i] == sourceIdx) {
      result[i] = 0
      continue
    }
    result[i] = -1 * Infinity
  }

  console.log('result:', result)

  // STEP 3: relax the edges n-1 times
  const relaxTimes = vertices.length - 1

  for (let i = 0; i < relaxTimes; i++) {
    for (let j = 0; j < edges.length; j++) {
      const [fromNode, toNode, weight] = edges[j]
      const newVal = Math.max(result[fromNode] + weight, result[toNode])

      console.log(`relax (${fromNode},${toNode}), val old: ${result[toNode]}, new: ${newVal}`)

      result[toNode] = newVal
    }
  }

  return JSON.stringify(result, null, 4)
}


console.log(`Longest path Bellman-Ford from node ${sourceIdx}:`, longestPathBellmanFord(graph, sourceIdx))
// console.log(`Longest path graph has POSITIVE cycle ${sourceIdx}:`, longestPathBellmanFord(graphWrong, sourceIdx))
$ node shortest-path-bellman-ford.js
vertices: [ 0, 1, 2, 3, 4 ]
edges: [ [ 0, 1, 4 ], [ 0, 3, 5 ], [ 1, 4, 7 ], [ 2, 1, 3 ], [ 3, 2, -10 ] ]
result: {
  '0': 0,
  '1': -Infinity,
  '2': -Infinity,
  '3': -Infinity,
  '4': -Infinity
}
...
Longest path Bellman-Ford from node 0: {
    "0": 0,
    "1": 4,
    "2": -5,
    "3": 5,
    "4": 11
}

The importance of finding longest, shortest paths

  • Bellman-Ford lists all possible shortest/longest paths. It's a "brute force" way.
  • Math proven means the algorithm guarantees the correct result.
  • It's crucial for optimization type 1 and type 2 problem: Maximization, Minimization.
  • Bellman-Ford is a proven procedure, too easy so it's rarely asked in coding interview.
  • But it's very important for our thinking process. That is if we can present our a problem in form of a graph, it's guaranteed to find max or min. And we can (almost) always present a problem in form of a graph.
Problem BFS Bellman-Ford
Shortest path (equal edges) O(n) O(E*V)
Shortest path (UN-equal edges) O(E*V)

Summary

Prob. class P. sub-class Input characteristics Algorithm Time
Complexity
(almost) ANY problem class array, matrix, ... Brute Force O(n) & up
Can try with any problem type - pass split test
- array, string or graph, ...
- divide, solve, combine gives a possible solution
Divide and Conquer O(nlogn)
- accessing 2 data points at a time is useful
- closing in the pointers proven to give a correct answer
- usually array or string
Two Pointers O(n)
1. Maximization
2. Minimization
array, string or graph, ... Greedy choice,
no guarantee for most optimal solution
varies
- graph or present problem with graph Bellman-Ford O(E*V)
3. Find all options array, matrix, ... Brute Force O(n) & up
4. Find most optimal solution - shortest/longest path
- Min/max
- graph or present problem with graph Bellman-Ford O(E*V)
5. Find a truth search for a number - sorted array
or balanced BST
Binary Search O(logn)
accessing a few consecutive data points at the same time is necessary/useful - usually array or string Slding Window O(n)
- shortest path, equal edge weight
- node visitation layer by layer
- graph or matrix Breadth First Search O(n)
- think top down, think bottom up
- node visitation go full depth first
- graph or matrix Depth First Search O(n)
- what must be done first? - graph or present problem with graph Topological Sort O(n)
- shortest/longest path - graph or present problem with graph Bellman-Ford O(E*V)

Real life interview questions

  1. How does Bellman-Ford solve problems?
  2. What kind of problems can typically be solved by Bellman-Ford?
  3. Recalculate the Complexity of the problems above.
  4. Redo the problems above. You should be able to quickly explain how the algorithm works and implement each of them within 15 mins.
  5. How can we use Bellman-Ford to detect if graph has cycle? Write the algorithm and what is your algorithm's' complexity?
  6. (Medium) Leetcode #743. Network Delay Time
  7. (Medium) Leetcode #787. Cheapest Flights Within K Stops
  8. (Medium) Leetcode #1631. Path With Minimum Effort
  9. (Medium) Leetcode #1514. Path with Maximum Probability
  10. (Medium) Leetcode #1334. Find the City With the Smallest Number of Neighbors at a Threshold Distance
  11. (Hard) Leetcode #1928. Minimum Cost to Reach Destination in Time