From 0dea90ceffe0ab2634dda843da77a724bccda6a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Fri, 25 Sep 2026 08:32:56 +0200 Subject: [PATCH] Update Dijkstra's algorithm to take any weights that satisfy the new Weight trait --- src/algorithms.rs | 61 +++++++++++++++++++++++++++++++++++------------ tests/dijkstra.rs | 8 ++++--- 2 files changed, 51 insertions(+), 18 deletions(-) diff --git a/src/algorithms.rs b/src/algorithms.rs index f42e4ad..d36653d 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -32,19 +32,46 @@ use priority_queue::PriorityQueue; use std::cmp::Reverse; use std::collections::VecDeque; use std::hash::Hash; +use std::ops::Add; use crate::maps::ElementMap; use crate::traits::{GraphTopology, Incidence, IncidenceCursor}; +/// Trait that edge weights for [`dijkstra`] and its variants need to satisfy. +/// +/// [`Default`] is used as the starting distance for `source`, so it must behave as the additive +/// identity (typically zero) for accumulation via [`Add`] to give correct results. +/// +/// # Examples +/// +/// ``` +/// # use grapherity::prelude::*; +/// # use grapherity::algorithms::dijkstra; +/// # use grapherity::models::Graph; +/// use std::time::Duration; +/// +/// let mut graph = Graph::new(); +/// let source = graph.add_vertex(); +/// let target = graph.add_vertex(); +/// let e = graph.add_edge(source, target); +/// let mut travel_time = graph.edge_map(Duration::ZERO); +/// travel_time[e] = Duration::from_secs(5); +/// +/// let result = dijkstra(&graph, source, |e| travel_time[e]); +/// assert_eq!(result.distances[target], Some(Duration::from_secs(5))); +/// ``` +pub trait Weight: Copy + Ord + Add + Default {} + +impl + Default> Weight for T {} + /// Return data type for [`dijkstra`] and [`dijkstra_unweighted`]. -pub struct DijkstraResult { +pub struct DijkstraResult { /// Vertex map of minimum distances from a given `source` vertex. - pub distances: ElementMap>, + pub distances: ElementMap>, /// Vertex map of predecessors on some shortest path from a given `source` vertex. pub predecessors: ElementMap>, } -// TODO: Generalize the return type of the weight function. // TODO: A Fibonacci heap would lower complexity to O(|E| + |V| log |V|) by making decrease-key O(1) amortized instead of O(log |V|). No standard Rust implementation exists; high constant factors may negate the asymptotic gain in practice. /// [Dijkstra's algorithm] with custom edge weights, returns minimum distances and predecessors. /// @@ -78,11 +105,15 @@ pub struct DijkstraResult { /// ``` /// /// [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm -pub fn dijkstra(graph: &G, source: G::Vertex, weights: W) -> DijkstraResult +pub fn dijkstra( + graph: &G, + source: G::Vertex, + weights: impl Fn(G::Edge) -> W, +) -> DijkstraResult where G: GraphTopology, G::Vertex: Hash, - W: Fn(G::Edge) -> u32, + W: Weight, { let mut predecessors = graph.vertex_map(None); let distances = dijkstra_impl(graph, source, weights, |adjacent, predecessor| { @@ -127,12 +158,12 @@ where pub fn dijkstra_distances( graph: &G, source: G::Vertex, - weights: W, -) -> ElementMap> + weights: impl Fn(G::Edge) -> W, +) -> ElementMap> where G: GraphTopology, G::Vertex: Hash, - W: Fn(G::Edge) -> u32, + W: Weight, { dijkstra_impl(graph, source, weights, |_, _| {}) } @@ -168,7 +199,7 @@ where /// ``` /// /// [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm -pub fn dijkstra_unweighted(graph: &G, source: G::Vertex) -> DijkstraResult +pub fn dijkstra_unweighted(graph: &G, source: G::Vertex) -> DijkstraResult where G: GraphTopology, G::Vertex: Hash, @@ -220,24 +251,24 @@ where fn dijkstra_impl( graph: &G, source: G::Vertex, - weights: W, + weights: impl Fn(G::Edge) -> W, mut on_relax: F, -) -> ElementMap> +) -> ElementMap> where G: GraphTopology, G::Vertex: Hash, - W: Fn(G::Edge) -> u32, + W: Weight, F: FnMut(G::Vertex, G::Vertex), { let mut distances = graph.vertex_map(None); let mut heap = PriorityQueue::new(); - distances[source] = Some(0); - heap.push(source, Reverse(0u32)); + distances[source] = Some(W::default()); + heap.push(source, Reverse(W::default())); while let Some((v, Reverse(v_distance))) = heap.pop() { for Incidence { vertex: u, edge: e } in graph.incidences(v) { - let new_distance = v_distance + weights(e); + let new_distance = weights(e) + v_distance; if match distances[u] { None => true, Some(old_distance) if old_distance > new_distance => true, diff --git a/tests/dijkstra.rs b/tests/dijkstra.rs index 802098a..36a69cc 100644 --- a/tests/dijkstra.rs +++ b/tests/dijkstra.rs @@ -173,7 +173,7 @@ where fixtures::assert_standard_unweighted_distances_v0(|v| distances[v], &vertices); } -fn assert_single_vertex(result: &DijkstraResult, v: G::Vertex) +fn assert_single_vertex(result: &DijkstraResult, v: G::Vertex) where G::Vertex: Debug, { @@ -195,8 +195,10 @@ fn assert_distances_single_vertex( ); } -fn assert_disconnected(result: &DijkstraResult, vertices: &[G::Vertex]) -where +fn assert_disconnected( + result: &DijkstraResult, + vertices: &[G::Vertex], +) where G::Vertex: Debug, { assert_distances_disconnected::(&result.distances, &vertices);