Bellman-Ford: Shortest Path with Negative Weights

Bellman-Ford Algorithm is a single-source shortest path algorithm designed to find the shortest path from a starting node to all other nodes in a weighted graph—including graphs with negative edge weights. Unlike Dijkstra’s algorithm (which fails with negative weights) and A* (which relies on heuristics), Bellman-Ford works by iteratively relaxing all edges in the graph and can also detect negative-weight cycles (cycles whose total edge weight is negative, making the shortest path undefined as looping the cycle reduces the path cost infinitely).

Bellman-Ford is less efficient than Dijkstra’s (O(VE) vs. O(E + V log V)) but is indispensable for graphs with negative weights or when negative cycle detection is required.


Core Principles of Bellman-Ford Algorithm

A graph with V vertices has a shortest path (without negative cycles) that contains at most V-1 edges—any longer path would include a cycle (which can be removed to shorten the path). Bellman-Ford leverages this property with the following steps:

1. Initialization

  • Assign a distance value of infinity to all nodes, except the source node (set to 0).
  • Maintain a parent array to reconstruct the shortest path (optional).

2. Edge Relaxation (V-1 Iterations)

For each iteration from 1 to V-1:

  • Traverse all edges in the graph. For an edge u → v with weight w:
    • If the current distance to v is greater than the distance to u plus w (distance[v] > distance[u] + w), relax the edge: update distance[v] = distance[u] + w and set parent[v] = u.

3. Negative Cycle Detection (1 Extra Iteration)

After V-1 relaxations, traverse all edges once more:

  • If any edge u → v can still be relaxed (distance[v] > distance[u] + w), the graph contains a negative-weight cycle reachable from the source node. The shortest path is undefined for nodes in or reachable from this cycle.

Key Term: Relaxation

Relaxation is the process of updating the shortest known distance to a node by finding a shorter path through another node. It is the core operation of all shortest path algorithms (Dijkstra’s, Bellman-Ford, Floyd-Warshall).


Bellman-Ford Algorithm Implementation (Python)

We implement Bellman-Ford for a graph represented by a list of edges (each edge is a tuple (u, v, w) where u is the source node, v is the destination, and w is the edge weight). The implementation includes shortest path calculation and negative cycle detection.

Full Implementation

python

运行

def bellman_ford(graph, vertices, source):
    """
    Bellman-Ford algorithm to find shortest paths from a source node and detect negative cycles.
    Args:
        graph (list): List of edges, each edge is a tuple (u, v, weight).
        vertices (int): Number of vertices in the graph (0-indexed).
        source (int): Source node for shortest path calculations.
    Returns:
        tuple: (distances, parent, has_negative_cycle)
            - distances: List of shortest distances from source to each node (infinity if unreachable).
            - parent: List to reconstruct the shortest path (None if no parent).
            - has_negative_cycle: Boolean indicating if a negative-weight cycle is reachable from the source.
    """
    # Step 1: Initialize distances and parent array
    INF = float('inf')
    distances = [INF] * vertices
    distances[source] = 0  # Distance to source is 0
    parent = [None] * vertices

    # Step 2: Relax all edges V-1 times
    for _ in range(vertices - 1):
        updated = False
        for u, v, w in graph:
            if distances[u] != INF and distances[v] > distances[u] + w:
                distances[v] = distances[u] + w
                parent[v] = u
                updated = True
        if not updated:
            break  # No more relaxations possible—early exit

    # Step 3: Check for negative-weight cycles (relax edges one more time)
    has_negative_cycle = False
    for u, v, w in graph:
        if distances[u] != INF and distances[v] > distances[u] + w:
            has_negative_cycle = True
            break  # Negative cycle detected

    return distances, parent, has_negative_cycle

def reconstruct_path(parent, source, target):
    """Reconstruct the shortest path from source to target using the parent array."""
    if parent[target] is None and source != target:
        return None  # No path exists
    path = []
    current = target
    while current is not None:
        path.append(current)
        current = parent[current]
    path.reverse()
    return path if path[0] == source else None

# Test the Bellman-Ford algorithm
if __name__ == "__main__":
    # Example 1: Graph with no negative cycles
    print("=== Graph 1 (No Negative Cycles) ===")
    vertices1 = 5
    graph1 = [
        (0, 1, -1),
        (0, 2, 4),
        (1, 2, 3),
        (1, 3, 2),
        (1, 4, 2),
        (3, 2, 5),
        (3, 1, 1),
        (4, 3, -3)
    ]
    source1 = 0
    distances1, parent1, has_cycle1 = bellman_ford(graph1, vertices1, source1)
    print(f"Shortest distances from source {source1}: {distances1}")
    print(f"Has negative cycle: {has_cycle1}")
    # Reconstruct path from 0 to 3
    target1 = 3
    path1 = reconstruct_path(parent1, source1, target1)
    print(f"Shortest path from {source1} to {target1}: {path1}\n")

    # Example 2: Graph with a negative-weight cycle
    print("=== Graph 2 (With Negative Cycle) ===")
    vertices2 = 4
    graph2 = [
        (0, 1, 1),
        (1, 2, -1),
        (2, 3, -1),
        (3, 1, -1)
    ]
    source2 = 0
    distances2, parent2, has_cycle2 = bellman_ford(graph2, vertices2, source2)
    print(f"Shortest distances from source {source2}: {distances2}")
    print(f"Has negative cycle: {has_cycle2}")
    target2 = 3
    path2 = reconstruct_path(parent2, source2, target2)
    print(f"Shortest path from {source2} to {target2}: {path2}")

Sample Output

plaintext

=== Graph 1 (No Negative Cycles) ===
Shortest distances from source 0: [0, -1, 2, -2, 1]
Has negative cycle: False
Shortest path from 0 to 3: [0, 1, 4, 3]

=== Graph 2 (With Negative Cycle) ===
Shortest distances from source 0: [0, 1, 0, -1]
Has negative cycle: True
Shortest path from 0 to 3: [0, 1, 2, 3]


Time and Space Complexity

MetricValueExplanation
Time ComplexityO(VE)V = number of vertices, E = number of edges. We relax E edges V-1 times (O(VE)), plus one extra iteration for negative cycle detection (O(E)).
Space ComplexityO(V)We store distances and parent arrays of size V (no extra space for edges—uses the input graph directly).

Optimization Note:

  • If no edges are relaxed in an iteration of the V-1 steps, we can exit early (as shown in the code with the updated flag). This reduces the time complexity for graphs where the shortest paths are found in fewer than V-1 iterations.

Pros and Cons of Bellman-Ford Algorithm

Pros

  1. Handles Negative Edge Weights: The only single-source shortest path algorithm (along with Floyd-Warshall) that works with negative edge weights.
  2. Detects Negative Cycles: Can identify if a negative-weight cycle is reachable from the source node—critical for problems where such cycles make the shortest path undefined.
  3. Simple to Implement: No complex data structures (e.g., priority queues in Dijkstra’s) are required; only edge traversal and array updates.
  4. Guaranteed Shortest Paths: For graphs without negative cycles, Bellman-Ford guarantees the shortest path from the source to all other nodes.

Cons

  1. Inefficient for Large Graphs: O(VE) time complexity is slower than Dijkstra’s O(E + V log V) for graphs with non-negative weights—avoid Bellman-Ford if negative weights are not present.
  2. No Heuristic Guidance: Unlike A*, Bellman-Ford explores all edges iteratively with no heuristic to prioritize relevant paths, leading to unnecessary computations.
  3. Only Single-Source: Computes shortest paths from one source node (unlike Floyd-Warshall, which is all-pairs). For multiple sources, Bellman-Ford must be run once per source (O(V²E) time).

Real-World Applications of Bellman-Ford Algorithm

  1. Network Routing Protocols: The Distance Vector Routing (DVR) protocol (used in early internet routing) is based on Bellman-Ford. It computes the shortest path between routers by exchanging distance vectors (path costs) with neighbors.
  2. Currency Arbitrage Detection: Bellman-Ford can detect arbitrage opportunities in currency exchange markets (a negative cycle in the exchange rate graph means risk-free profit by looping through currencies).
  3. Traffic Routing: Used in traffic navigation systems to account for negative edge weights (e.g., toll discounts, HOV lane benefits that reduce travel time/cost).
  4. Robotics: Path planning for robots in environments where certain paths have negative costs (e.g., reward zones that offset movement costs).
  5. Circuit Design: Analyzing signal delay in electronic circuits, where some paths may have negative delays (due to signal amplification) and negative cycles indicate unstable circuits.

Summary

Best use cases: Graphs with negative edge weights, negative cycle detection, and network routing protocols.

Bellman-Ford is a single-source shortest path algorithm that handles negative edge weights and detects negative-weight cycles.

Core steps: Initialize distances → relax all edges V-1 times → check for negative cycles with one extra relaxation.

Time complexity: O(VE); Space complexity: O(V).



了解 Ruigu Electronic 的更多信息

订阅后即可通过电子邮件收到最新文章。

Posted in

Leave a comment