Skip to main content

BellmanFord

Bellman-Ford shortest-path algorithm supporting negative edge weights.

Unlike Dijkstra, Bellman-Ford can handle negative edge weights. It works by relaxing all edges |V| − 1 times, then performing one final relaxation pass to detect negative-weight cycles (cycles where the total weight is less than zero). If such a cycle exists, no well-defined shortest path can be computed for vertices reachable through it.

When to use each algorithm
Scenario Algorithm
All weights ≥ 0, single target Dijkstra.shortestPath
All weights ≥ 0, weighted traversal Dijkstra.toIterator
All weights ≥ 0, heuristic available AStar.shortestPath
Negative weights possible BellmanFord

allVertices parameter

Bellman-Ford needs to enumerate every edge in the graph, which requires knowing every vertex. Pass the complete list as allVertices: { V }. Vertices reachable from start that are not in allVertices are treated as unreachable.

All functions accept either callback form for GetNeighbors — see GraphUtil for details.

Example

--        1         3
--  A ──→ B ──────→ D
--        │
--       -2
--        ↓
--        C

local weights = {
    A = { B = 1 },
    B = { C = -2, D = 3 },
    C = {},
    D = {},
}
local allVertices = { "A", "B", "C", "D" }

local function getNeighbors(v) return ... end
local function getWeight(from, to) return weights[from][to] end

local path, hasNegativeCycle = BellmanFord.shortestPath("A", "C", allVertices, getNeighbors, getWeight)
-- path.path  == { "A", "B", "C" }
-- path.cost  == -1   (1 + -2)
-- hasNegativeCycle == false

Functions

shortestPath

directedundirected
BellmanFord.shortestPath(
startV,
goalV,
allVertices{V},
getNeighborsGetNeighbors<V>,
getWeightGetWeight<V>
) → (
boolean
)

Finds the shortest weighted path from start to goal, returning a (PathResult<V>?, boolean) tuple.

  • First return — a PathResult if a path exists and no negative cycle was detected; nil otherwise.
  • Second returntrue if a negative-weight cycle was detected in the reachable subgraph from start (in which case the first return is always nil).
local result, hasNegCycle = BellmanFord.shortestPath(
    "A", "C", allVertices, getNeighbors, getWeight
)
if hasNegCycle then
    warn("Graph contains a negative-weight cycle")
elseif result == nil then
    warn("Goal is unreachable")
else
    print("Cost:", result.cost)
end

Time complexity: O(V · E)

distances

directedundirected
BellmanFord.distances(
startV,
allVertices{V},
getNeighborsGetNeighbors<V>,
getWeightGetWeight<V>
) → (
{[V]number},
boolean
)

Runs Bellman-Ford from start and returns all shortest distances from start to every vertex in allVertices, alongside a negative-cycle flag.

  • First return{ [V]: number } mapping each reachable vertex to its shortest distance from start. Vertices in allVertices that are unreachable have a value of math.huge.
  • Second returntrue if a negative-weight cycle was detected. Distances may be incorrect for vertices affected by the cycle.
local distances, hasNegCycle = BellmanFord.distances("A", allVertices, getNeighbors, getWeight)
if not hasNegCycle then
    print(distances["D"]) -- shortest dist A→D
end

Time complexity: O(V · E)

Show raw api
{
    "functions": [
        {
            "name": "shortestPath",
            "desc": "Finds the shortest weighted path from `start` to `goal`, returning a\n`(PathResult<V>?, boolean)` tuple.\n\n* **First return** — a [PathResult](/api/GraphUtil#PathResult) if a path\n  exists and no negative cycle was detected; `nil` otherwise.\n* **Second return** — `true` if a negative-weight cycle was detected in\n  the reachable subgraph from `start` (in which case the first return is\n  always `nil`).\n\n```lua\nlocal result, hasNegCycle = BellmanFord.shortestPath(\n    \"A\", \"C\", allVertices, getNeighbors, getWeight\n)\nif hasNegCycle then\n    warn(\"Graph contains a negative-weight cycle\")\nelseif result == nil then\n    warn(\"Goal is unreachable\")\nelse\n    print(\"Cost:\", result.cost)\nend\n```\n\n*Time complexity:* `O(V · E)`",
            "params": [
                {
                    "name": "start",
                    "desc": "",
                    "lua_type": "V"
                },
                {
                    "name": "goal",
                    "desc": "",
                    "lua_type": "V"
                },
                {
                    "name": "allVertices",
                    "desc": "",
                    "lua_type": "{ V }"
                },
                {
                    "name": "getNeighbors",
                    "desc": "",
                    "lua_type": "GetNeighbors<V>"
                },
                {
                    "name": "getWeight",
                    "desc": "",
                    "lua_type": "GetWeight<V>\n"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "PathResult<V>?"
                },
                {
                    "desc": "",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "static",
            "tags": [
                "directed",
                "undirected"
            ],
            "source": {
                "line": 99,
                "path": "lib/graphutil/src/BellmanFord.luau"
            }
        },
        {
            "name": "distances",
            "desc": "Runs Bellman-Ford from `start` and returns all shortest distances from\n`start` to every vertex in `allVertices`, alongside a negative-cycle flag.\n\n* **First return** — `{ [V]: number }` mapping each reachable vertex to\n  its shortest distance from `start`.  Vertices in `allVertices` that are\n  unreachable have a value of `math.huge`.\n* **Second return** — `true` if a negative-weight cycle was detected.\n  Distances may be incorrect for vertices affected by the cycle.\n\n```lua\nlocal distances, hasNegCycle = BellmanFord.distances(\"A\", allVertices, getNeighbors, getWeight)\nif not hasNegCycle then\n    print(distances[\"D\"]) -- shortest dist A→D\nend\n```\n\n*Time complexity:* `O(V · E)`",
            "params": [
                {
                    "name": "start",
                    "desc": "",
                    "lua_type": "V"
                },
                {
                    "name": "allVertices",
                    "desc": "",
                    "lua_type": "{ V }"
                },
                {
                    "name": "getNeighbors",
                    "desc": "",
                    "lua_type": "GetNeighbors<V>"
                },
                {
                    "name": "getWeight",
                    "desc": "",
                    "lua_type": "GetWeight<V>\n"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "{ [V]: number }"
                },
                {
                    "desc": "",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "static",
            "tags": [
                "directed",
                "undirected"
            ],
            "source": {
                "line": 191,
                "path": "lib/graphutil/src/BellmanFord.luau"
            }
        }
    ],
    "properties": [],
    "types": [],
    "name": "BellmanFord",
    "desc": "Bellman-Ford shortest-path algorithm supporting negative edge weights.\n\nUnlike Dijkstra, Bellman-Ford can handle **negative edge weights**.  It\nworks by relaxing all edges `|V| − 1` times, then performing one final\nrelaxation pass to detect **negative-weight cycles** (cycles where the\ntotal weight is less than zero).  If such a cycle exists, no well-defined\nshortest path can be computed for vertices reachable through it.\n\n:::tip When to use each algorithm\n| Scenario | Algorithm |\n| --- | --- |\n| All weights ≥ 0, single target | [Dijkstra.shortestPath](/api/Dijkstra#shortestPath) |\n| All weights ≥ 0, weighted traversal | [Dijkstra.toIterator](/api/Dijkstra#toIterator) |\n| All weights ≥ 0, heuristic available | [AStar.shortestPath](/api/AStar#shortestPath) |\n| Negative weights possible | **BellmanFord** |\n:::\n\n### `allVertices` parameter\n\nBellman-Ford needs to enumerate every edge in the graph, which requires\nknowing every vertex.  Pass the complete list as `allVertices: { V }`.\nVertices reachable from `start` that are **not** in `allVertices` are\ntreated as unreachable.\n\nAll functions accept either callback form for `GetNeighbors` — see\n[GraphUtil](/api/GraphUtil) for details.\n\n### Example\n\n```lua\n--        1         3\n--  A ──→ B ──────→ D\n--        │\n--       -2\n--        ↓\n--        C\n\nlocal weights = {\n    A = { B = 1 },\n    B = { C = -2, D = 3 },\n    C = {},\n    D = {},\n}\nlocal allVertices = { \"A\", \"B\", \"C\", \"D\" }\n\nlocal function getNeighbors(v) return ... end\nlocal function getWeight(from, to) return weights[from][to] end\n\nlocal path, hasNegativeCycle = BellmanFord.shortestPath(\"A\", \"C\", allVertices, getNeighbors, getWeight)\n-- path.path  == { \"A\", \"B\", \"C\" }\n-- path.cost  == -1   (1 + -2)\n-- hasNegativeCycle == false\n```",
    "source": {
        "line": 61,
        "path": "lib/graphutil/src/BellmanFord.luau"
    }
}