Update Dijkstra's algorithm to take any weights that satisfy the new Weight trait

This commit is contained in:
2026-09-25 08:32:56 +02:00
parent 64c48287a7
commit 0dea90ceff
2 changed files with 51 additions and 18 deletions
+46 -15
View File
@@ -32,19 +32,46 @@ use priority_queue::PriorityQueue;
use std::cmp::Reverse; use std::cmp::Reverse;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::hash::Hash; use std::hash::Hash;
use std::ops::Add;
use crate::maps::ElementMap; use crate::maps::ElementMap;
use crate::traits::{GraphTopology, Incidence, IncidenceCursor}; 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<Output = Self> + Default {}
impl<T: Copy + Ord + Add<Output = T> + Default> Weight for T {}
/// Return data type for [`dijkstra`] and [`dijkstra_unweighted`]. /// Return data type for [`dijkstra`] and [`dijkstra_unweighted`].
pub struct DijkstraResult<V: Copy> { pub struct DijkstraResult<V: Copy, W: Weight> {
/// Vertex map of minimum distances from a given `source` vertex. /// Vertex map of minimum distances from a given `source` vertex.
pub distances: ElementMap<V, Option<u32>>, pub distances: ElementMap<V, Option<W>>,
/// Vertex map of predecessors on some shortest path from a given `source` vertex. /// Vertex map of predecessors on some shortest path from a given `source` vertex.
pub predecessors: ElementMap<V, Option<V>>, pub predecessors: ElementMap<V, Option<V>>,
} }
// 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. // 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. /// [Dijkstra's algorithm] with custom edge weights, returns minimum distances and predecessors.
/// ///
@@ -78,11 +105,15 @@ pub struct DijkstraResult<V: Copy> {
/// ``` /// ```
/// ///
/// [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm /// [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
pub fn dijkstra<G, W>(graph: &G, source: G::Vertex, weights: W) -> DijkstraResult<G::Vertex> pub fn dijkstra<G, W>(
graph: &G,
source: G::Vertex,
weights: impl Fn(G::Edge) -> W,
) -> DijkstraResult<G::Vertex, W>
where where
G: GraphTopology, G: GraphTopology,
G::Vertex: Hash, G::Vertex: Hash,
W: Fn(G::Edge) -> u32, W: Weight,
{ {
let mut predecessors = graph.vertex_map(None); let mut predecessors = graph.vertex_map(None);
let distances = dijkstra_impl(graph, source, weights, |adjacent, predecessor| { let distances = dijkstra_impl(graph, source, weights, |adjacent, predecessor| {
@@ -127,12 +158,12 @@ where
pub fn dijkstra_distances<G, W>( pub fn dijkstra_distances<G, W>(
graph: &G, graph: &G,
source: G::Vertex, source: G::Vertex,
weights: W, weights: impl Fn(G::Edge) -> W,
) -> ElementMap<G::Vertex, Option<u32>> ) -> ElementMap<G::Vertex, Option<W>>
where where
G: GraphTopology, G: GraphTopology,
G::Vertex: Hash, G::Vertex: Hash,
W: Fn(G::Edge) -> u32, W: Weight,
{ {
dijkstra_impl(graph, source, weights, |_, _| {}) dijkstra_impl(graph, source, weights, |_, _| {})
} }
@@ -168,7 +199,7 @@ where
/// ``` /// ```
/// ///
/// [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm /// [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
pub fn dijkstra_unweighted<G>(graph: &G, source: G::Vertex) -> DijkstraResult<G::Vertex> pub fn dijkstra_unweighted<G>(graph: &G, source: G::Vertex) -> DijkstraResult<G::Vertex, u32>
where where
G: GraphTopology, G: GraphTopology,
G::Vertex: Hash, G::Vertex: Hash,
@@ -220,24 +251,24 @@ where
fn dijkstra_impl<G, W, F>( fn dijkstra_impl<G, W, F>(
graph: &G, graph: &G,
source: G::Vertex, source: G::Vertex,
weights: W, weights: impl Fn(G::Edge) -> W,
mut on_relax: F, mut on_relax: F,
) -> ElementMap<G::Vertex, Option<u32>> ) -> ElementMap<G::Vertex, Option<W>>
where where
G: GraphTopology, G: GraphTopology,
G::Vertex: Hash, G::Vertex: Hash,
W: Fn(G::Edge) -> u32, W: Weight,
F: FnMut(G::Vertex, G::Vertex), F: FnMut(G::Vertex, G::Vertex),
{ {
let mut distances = graph.vertex_map(None); let mut distances = graph.vertex_map(None);
let mut heap = PriorityQueue::new(); let mut heap = PriorityQueue::new();
distances[source] = Some(0); distances[source] = Some(W::default());
heap.push(source, Reverse(0u32)); heap.push(source, Reverse(W::default()));
while let Some((v, Reverse(v_distance))) = heap.pop() { while let Some((v, Reverse(v_distance))) = heap.pop() {
for Incidence { vertex: u, edge: e } in graph.incidences(v) { 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] { if match distances[u] {
None => true, None => true,
Some(old_distance) if old_distance > new_distance => true, Some(old_distance) if old_distance > new_distance => true,
+5 -3
View File
@@ -173,7 +173,7 @@ where
fixtures::assert_standard_unweighted_distances_v0(|v| distances[v], &vertices); fixtures::assert_standard_unweighted_distances_v0(|v| distances[v], &vertices);
} }
fn assert_single_vertex<G: GraphTopology>(result: &DijkstraResult<G::Vertex>, v: G::Vertex) fn assert_single_vertex<G: GraphTopology>(result: &DijkstraResult<G::Vertex, u32>, v: G::Vertex)
where where
G::Vertex: Debug, G::Vertex: Debug,
{ {
@@ -195,8 +195,10 @@ fn assert_distances_single_vertex<G: GraphTopology>(
); );
} }
fn assert_disconnected<G: GraphTopology>(result: &DijkstraResult<G::Vertex>, vertices: &[G::Vertex]) fn assert_disconnected<G: GraphTopology>(
where result: &DijkstraResult<G::Vertex, u32>,
vertices: &[G::Vertex],
) where
G::Vertex: Debug, G::Vertex: Debug,
{ {
assert_distances_disconnected::<G>(&result.distances, &vertices); assert_distances_disconnected::<G>(&result.distances, &vertices);