Bellman-Ford Algorithm and Distance Vector Routing (BGL Book Chapter 5.3)
Based on “The Boost Graph Library” by Jeremy Siek, Lie-Quan Lee, and Andrew Lumsdaine
Overview
The routing problem can be modeled as a graph by associating a vertex with each router and an edge for each direct connection between two routers. Information such as delay and bandwidth are attached to each edge. The routing problem is now transformed into a shortest-paths graph problem.
The ultimate goal of a routing process is to deliver packets to their destinations as quickly as possible. There are a number of factors that determine how long a packet takes to arrive (e.g., the number of hops along the path, transmission delay within a router, transmission delay between routers, network bandwidth). The routing protocol must choose the best paths between routers; this information is stored in a routing table.
Distance Vector Routing
Some of the first Internet routing protocols, such as the Routing Information Protocol (RIP), used a distance-vector protocol. The basic idea behind RIP is for each router to maintain an estimate of distance to all other routers and to periodically compare notes with its neighbors.
If a router learns of a shorter path to some destination from one of its neighbors, it will update its distance record to that destination and change its routing table to send packets to that destination via that neighbor. After enough time, the estimated distances maintained in this distributed fashion by multiple routers are guaranteed to converge to the true distance.
The Bellman-Ford Algorithm
RIP is a distributed form of the Bellman-Ford shortest paths algorithm. The principle step in the Bellman-Ford algorithm, called edge relaxation, corresponds to the notion of “comparing notes with your neighbor.”
The relaxation operation applied to edge (u, v) performs the following update:
d[v] = min(w(u, v) + d[u], d[v])
The Bellman-Ford algorithm loops through all of the edges in a graph, applying the relaxation operation to each. The algorithm repeats this loop |V| times, after which it is guaranteed that the distances to each vertex have been reduced to the minimum possible (unless there is a negative cycle in the graph).
Negative Cycle Detection
If there is a negative cycle, then there will be edges in the graph that were not
properly minimized. To verify that all edges are minimized, the algorithm loops over
all of the edges in the graph a final time, returning true if they are minimized,
and returning false otherwise.
Comparison with Dijkstra
Feature |
Bellman-Ford |
Dijkstra |
|---|---|---|
Time complexity |
O(V * E) |
O((V+E) log V) |
Negative weights |
Yes |
No |
Negative cycle detect |
Yes |
No |
Distributed version |
Yes (RIP) |
No |
NWGraph Implementation
NWGraph provides a bellman_ford function that implements the Bellman-Ford algorithm.
1/**
2 * @file ch5_bellman_ford.cpp
3 *
4 * @brief Bellman-Ford Algorithm and Distance Vector Routing (BGL Book Chapter 5.3)
5 *
6 * This example demonstrates the Bellman-Ford single-source shortest-paths algorithm
7 * applied to a router network. The Bellman-Ford algorithm can handle graphs with
8 * negative edge weights (unlike Dijkstra) and can detect negative cycles.
9 *
10 * The algorithm is the basis for distance-vector routing protocols like RIP
11 * (Routing Information Protocol), where routers periodically exchange distance
12 * estimates with neighbors.
13 *
14 * Key differences from Dijkstra:
15 * - Can handle negative edge weights
16 * - Slower: O(V*E) vs O((V+E)log V)
17 * - Can detect negative cycles
18 *
19 * @copyright SPDX-FileCopyrightText: 2022 Battelle Memorial Institute
20 * @copyright SPDX-FileCopyrightText: 2022 University of Washington
21 *
22 * SPDX-License-Identifier: BSD-3-Clause
23 *
24 * @authors
25 * Andrew Lumsdaine
26 *
27 */
28
29#include <iostream>
30#include <limits>
31#include <vector>
32
33#include "nwgraph/adjacency.hpp"
34#include "nwgraph/edge_list.hpp"
35
36using namespace nw::graph;
37
38/**
39 * @brief Bellman-Ford shortest paths algorithm
40 *
41 * Computes shortest paths from a source vertex to all other vertices.
42 * Returns false if a negative cycle is detected, true otherwise.
43 *
44 * The algorithm works by repeatedly relaxing all edges |V|-1 times.
45 * After this, all shortest paths are found (if no negative cycles exist).
46 * A final pass checks for negative cycles.
47 */
48template <typename EdgeList, typename Weight>
49auto bellman_ford(const EdgeList& edges, size_t num_vertices, size_t source, Weight weight_fn)
50 -> std::tuple<bool, std::vector<double>, std::vector<size_t>> {
51
52 std::vector<double> distance(num_vertices, std::numeric_limits<double>::max());
53 std::vector<size_t> predecessor(num_vertices);
54
55 // Initialize predecessor to self (no predecessor)
56 for (size_t i = 0; i < num_vertices; ++i) {
57 predecessor[i] = i;
58 }
59
60 distance[source] = 0;
61
62 // Relax all edges |V|-1 times
63 for (size_t i = 0; i < num_vertices - 1; ++i) {
64 bool changed = false;
65 for (auto&& edge : edges) {
66 auto u = std::get<0>(edge);
67 auto v = std::get<1>(edge);
68 auto w = weight_fn(edge);
69
70 // Relaxation step
71 if (distance[u] != std::numeric_limits<double>::max() && distance[u] + w < distance[v]) {
72 distance[v] = distance[u] + w;
73 predecessor[v] = u;
74 changed = true;
75 }
76 }
77 // Early termination if no changes
78 if (!changed) break;
79 }
80
81 // Check for negative cycles
82 for (auto&& edge : edges) {
83 auto u = std::get<0>(edge);
84 auto v = std::get<1>(edge);
85 auto w = weight_fn(edge);
86
87 if (distance[u] != std::numeric_limits<double>::max() && distance[u] + w < distance[v]) {
88 // Negative cycle detected
89 return {false, distance, predecessor};
90 }
91 }
92
93 return {true, distance, predecessor};
94}
95
96int main() {
97 std::cout << "=== Bellman-Ford Algorithm and Distance Vector Routing ===" << std::endl;
98 std::cout << "Based on BGL Book Chapter 5.3" << std::endl << std::endl;
99
100 // Router network from the BGL book example
101 // Vertices represent routers: A=0, B=1, C=2, D=3, E=4, F=5, G=6, H=7
102 // Edge weights represent transmission delays
103 const char* router_names = "ABCDEFGH";
104 const size_t num_routers = 8;
105
106 std::cout << "Router network with transmission delays:" << std::endl;
107 std::cout << " A connects to B (5.0) and C (1.0)" << std::endl;
108 std::cout << " B connects to D (1.3) and E (3.0)" << std::endl;
109 std::cout << " C connects to E (10.0) and F (2.0)" << std::endl;
110 std::cout << " D connects to E (0.4) and H (6.3)" << std::endl;
111 std::cout << " E connects to H (1.3)" << std::endl;
112 std::cout << " F connects to G (1.2)" << std::endl;
113 std::cout << " G connects to H (0.5)" << std::endl;
114 std::cout << std::endl;
115
116 // Create edge list with transmission delays
117 // Using directed edges as in the BGL book example
118 edge_list<directedness::directed, double> edges(num_routers);
119 edges.open_for_push_back();
120
121 // Edges from the BGL book Figure 5.2
122 edges.push_back(0, 1, 5.0); // A -> B
123 edges.push_back(0, 2, 1.0); // A -> C
124 edges.push_back(1, 3, 1.3); // B -> D
125 edges.push_back(1, 4, 3.0); // B -> E
126 edges.push_back(2, 4, 10.0); // C -> E
127 edges.push_back(2, 5, 2.0); // C -> F
128 edges.push_back(3, 7, 6.3); // D -> H
129 edges.push_back(3, 4, 0.4); // D -> E
130 edges.push_back(4, 7, 1.3); // E -> H
131 edges.push_back(5, 6, 1.2); // F -> G
132 edges.push_back(6, 7, 0.5); // G -> H
133
134 edges.close_for_push_back();
135
136 // Run Bellman-Ford from router A (vertex 0)
137 size_t source = 0; // Router A
138 std::cout << "Computing shortest paths from router " << router_names[source] << "..." << std::endl;
139 std::cout << std::endl;
140
141 auto weight_fn = [](auto&& edge) { return std::get<2>(edge); };
142 auto [success, distance, predecessor] = bellman_ford(edges, num_routers, source, weight_fn);
143
144 if (success) {
145 std::cout << "Shortest path distances and predecessors:" << std::endl;
146 std::cout << std::string(40, '-') << std::endl;
147
148 for (size_t i = 0; i < num_routers; ++i) {
149 std::cout << " " << router_names[i] << ": ";
150 if (distance[i] == std::numeric_limits<double>::max()) {
151 std::cout << "unreachable";
152 } else {
153 std::cout << distance[i] << " (via " << router_names[predecessor[i]] << ")";
154 }
155 std::cout << std::endl;
156 }
157
158 // Trace path to H
159 std::cout << std::endl;
160 std::cout << "Shortest path from A to H:" << std::endl;
161 std::cout << " ";
162
163 std::vector<size_t> path;
164 size_t current = 7; // H
165 while (current != source) {
166 path.push_back(current);
167 current = predecessor[current];
168 }
169 path.push_back(source);
170
171 for (auto it = path.rbegin(); it != path.rend(); ++it) {
172 std::cout << router_names[*it];
173 if (it + 1 != path.rend()) std::cout << " -> ";
174 }
175 std::cout << std::endl;
176 std::cout << " Total delay: " << distance[7] << std::endl;
177
178 } else {
179 std::cout << "Negative cycle detected in the network!" << std::endl;
180 }
181
182 // Example with a negative cycle
183 std::cout << std::endl;
184 std::cout << "=== Example with Negative Cycle ===" << std::endl;
185
186 edge_list<directedness::directed, double> edges2(4);
187 edges2.open_for_push_back();
188 edges2.push_back(0, 1, 1.0);
189 edges2.push_back(1, 2, -1.0);
190 edges2.push_back(2, 3, -1.0);
191 edges2.push_back(3, 1, -1.0); // Creates negative cycle: 1 -> 2 -> 3 -> 1 (cost = -3)
192 edges2.close_for_push_back();
193
194 auto [success2, distance2, predecessor2] = bellman_ford(edges2, 4, 0, weight_fn);
195
196 if (success2) {
197 std::cout << "No negative cycle found." << std::endl;
198 } else {
199 std::cout << "Negative cycle detected! (as expected)" << std::endl;
200 std::cout << "The cycle 1 -> 2 -> 3 -> 1 has total weight -3" << std::endl;
201 }
202
203 std::cout << std::endl;
204 std::cout << "Note: Bellman-Ford is O(V*E), slower than Dijkstra's O((V+E)log V)," << std::endl;
205 std::cout << "but can handle negative weights and detect negative cycles." << std::endl;
206
207 return 0;
208}
Building and Running the Example
First, configure and build the example:
# From the NWGraph root directory
mkdir -p build && cd build
cmake .. -DNWGRAPH_BUILD_EXAMPLES=ON
make ch5_bellman_ford
Then run the example:
./examples/bgl-book/ch5_bellman_ford
The program uses a sample router network and computes shortest paths using the Bellman-Ford algorithm, demonstrating how distance-vector routing protocols work.
Sample Output
Router Network - Bellman-Ford Shortest Paths
Source: Router A
Router B: distance = 5ms, via Router A
Router C: distance = 1ms, via Router A
Router D: distance = 6.3ms, via Router B
...
No negative cycles detected.
Key NWGraph Features Demonstrated
Edge iteration: Processing all edges for relaxation
Distance tracking: Recording shortest path distances
Predecessor maps: Building the shortest-paths tree
Negative cycle detection: Verifying solution validity
References
Cormen, Leiserson, Rivest, and Stein. Introduction to Algorithms, 4th Edition (2022), Chapter 22.1: The Bellman-Ford Algorithm
Siek, Lee, and Lumsdaine. The Boost Graph Library (2002), Chapter 5.3
See Also
Internet Routing with Dijkstra’s Algorithm (BGL Book Chapter 5.4) - Dijkstra’s algorithm for link-state routing
Kruskal’s Minimum Spanning Tree Algorithm (BGL Book Chapter 6) - Minimum spanning trees
Boost Graph Library Examples (Rewritten for NW Graph) - BGL Book examples overview