defmodule Pathfindings.BellmanFord do @moduledoc """ The Bellman–Ford algorithm is an algorithm that computes shortest paths from a single source vertex to all of the other vertices in a weighted digraph. It is capable of handling graphs in which some of the edge weights are negative numbers Time complexity: O(VLogV) """ import Graph.Utils, only: [vertex_id: 1] @type distance :: %{Graph.vertex_id => integer} @doc """ Returns nil when graph has negative cycle. """ @spec call(Graph.t, Graph.vertex) :: [Graph.vertex] | nil def call(%Graph{vertices: vs, edges: meta}, a) do distances = a |> vertex_id |> init_distances(vs) distances = vs |> Enum.reduce(distances, fn (_vertex, distances) -> meta |> Enum.reduce(distances, &update_distance(&1, &2)) end) if has_negative_cycle?(distances, meta) do nil else distances end end @spec init_distances(Graph.vertex, Graph.vertices) :: distance defp init_distances(vertex_id, vertices) do Enum.reduce(vertices, Map.new, fn {id, _vertex}, dist -> Map.put(dist, id, init_distance_value(id, vertex_id)) end) end defp init_distance_value(vertex_id, id) when vertex_id == id, do: 0 defp init_distance_value(_, _), do: :infinity @spec update_distance(term, distance) :: distance defp update_distance({{u, v}, _} = edge, distances) do weight = edge_weight(edge) if distances[u] != :infinity and distances[u] + weight < distances[v] do Map.replace!(distances, v, distances[u] + weight) else distances end end @spec edge_weight(term) :: float defp edge_weight({_, edge_value}), do: edge_value |> Map.values |> List.first defp has_negative_cycle?(distances, meta) do meta |> Enum.reduce(false, fn({{u, v}, _} = edge, has_cycle) -> weight = edge_weight(edge) has_cycle or distances[u] != :infinity and distances[u] + weight < distances[v] end) end end