Bellman-Ford
Finds the shortest distance from a start vertex to every other vertex, even with negative weights, and reports a negative cycle when the start can reach one.
Time complexity O(V·E), gograph function path.BellmanFord.
How it works
Bellman-Ford starts like Dijkstra, with 0 at the start and infinity everywhere else, but it doesn't take vertices in order of distance. It goes through every edge, and when an edge gives its end a shorter distance, it lowers that distance. It repeats this pass one time fewer than there are vertices. A shortest path that doesn't repeat a vertex has at most that many edges, so after the last pass every distance is final.
Then it makes one more pass. If an edge can still shorten a distance, the start can reach a cycle whose weights add up to less than zero. Such a graph has no shortest path, since each trip around the cycle makes the path shorter, and path.BellmanFord returns ErrNegativeWeightCycle. It needs a directed, weighted graph and costs O(V·E).
Use it when some weights are negative, such as refunds, rewards or the negative logarithms of exchange rates, where a negative cycle is a chance for arbitrage. When no weight is negative, Dijkstra gives the same distances faster.
Arbitrage loop
Eight currencies, where the quotes between GBP and JPY lag behind the others. Each edge is a trade, and its weight is minus the natural log of the exchange rate. Trading USD for GBP, GBP for JPY and JPY back to USD ends with about 0.2% more dollars than it started with, so that loop has a negative total weight. The rates are illustrative and close to market rates.
A directed, weighted graph with 8 vertices and 24 edges.
How much of each currency can a dollar buy at the best rate, and is there a loop of trades that ends with more dollars than it started with?
There is no best rate. USD can reach a loop of trades that ends with more than it started with, so path.BellmanFord returns path.ErrNegativeWeightCycle.
Step by step
- Every distance starts at ∞, except USD, which is 0. Bellman-Ford checks all 24 edges in each of 7 passes, one fewer than the 8 vertices, then once more to look for a negative cycle.
- Pass 1. USD to EUR at 0.90818: 0 + 0.0963 = 0.0963, so EUR's distance becomes 0.0963: 1 USD buys 0.90818 EUR this way.
- Pass 1. USD to GBP at 0.76808: 0 + 0.2639 = 0.2639, so GBP's distance becomes 0.2639: 1 USD buys 0.76808 GBP this way.
- Pass 1. USD to JPY at 149.85: 0 - 5.0096 = -5.0096, so JPY's distance becomes -5.0096: 1 USD buys 149.85 JPY this way.
- Pass 1. USD to CHF at 0.8449: 0 + 0.1685 = 0.1685, so CHF's distance becomes 0.1685: 1 USD buys 0.8449 CHF this way.
- Pass 1. USD to CAD at 1.3632: 0 - 0.3098 = -0.3098, so CAD's distance becomes -0.3098: 1 USD buys 1.3632 CAD this way.
63 more steps lead to the last one:
- Extra pass. EUR to CHF at 0.93313: 0.0846 + 0.0692 = 0.1538, still shorter than 0.1558, so the distances never settle. The loop USD, GBP, JPY, USD turns 1 USD into 1.002 USD, and each lap lowers them again. gograph stops and returns path.ErrNegativeWeightCycle.
Use it in Go
dist, err := path.BellmanFord(g, "USD")
if err != nil {
return err // path.ErrNegativeWeightCycle
}
fmt.Println(dist["NZD"])
Example graphs
- School run, 6 vertices and 13 edges
- Major currencies, 6 vertices and 20 edges
- Delivery area, 8 vertices and 12 edges
- Arbitrage loop, 8 vertices and 24 edges
- One-way city, 45 vertices and 88 edges
More in Shortest paths
- Dijkstra: Finds the shortest distance from a start vertex to every other vertex when no edge weight is negative.
- Floyd-Warshall: Finds the shortest distance between every pair of vertices, even with negative weights, by letting paths stop at one more vertex in each round.