From 110e6310aab8fc7ed573232670ac95f450573bdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20M=C3=BCller?= Date: Thu, 6 Aug 2026 09:44:28 +0200 Subject: [PATCH] Use PriorityQueue for Dijkstra's, requires Hash for Vertex --- Cargo.toml | 1 + src/algorithms.rs | 59 ++++++++++++++++---------------------- src/models/append_graph.rs | 4 +-- 3 files changed, 28 insertions(+), 36 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 513fad2..4129fd2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,5 @@ keywords = ["graph", "graph-algorithms"] categories = ["algorithms", "data-structures", "mathematics"] [dependencies] +priority-queue = "2.7.0" typed-generational-arena = "0.2.9" diff --git a/src/algorithms.rs b/src/algorithms.rs index 17fb72f..2386965 100644 --- a/src/algorithms.rs +++ b/src/algorithms.rs @@ -28,30 +28,14 @@ //! [Depth-first search]: https://en.wikipedia.org/wiki/Depth-first_search //! [Dijkstra's algorithm]: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm -use std::cmp::Ordering; -use std::collections::{BinaryHeap, VecDeque}; +use priority_queue::PriorityQueue; +use std::cmp::Reverse; +use std::collections::VecDeque; +use std::hash::Hash; use crate::maps::EntityMap; use crate::traits::{GraphTopology, IncidenceCursor}; -#[derive(PartialEq, Eq)] -struct DistanceOrderedVertex { - distance: u32, - vertex: V, -} - -impl PartialOrd for DistanceOrderedVertex { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for DistanceOrderedVertex { - fn cmp(&self, other: &Self) -> Ordering { - other.distance.cmp(&self.distance) - } -} - /// Return data type for [`dijkstra`] and [`dijkstra_unweighted`]. pub struct DijkstraResult { /// Vertex map of minimum distances from a given `source` vertex. @@ -61,7 +45,7 @@ pub struct DijkstraResult { } // TODO: Generalize the return type of the weight function. -// TODO: Add complexity information for Dijkstra's algorithm variants. +// 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. /// /// Calculates the shortest paths from `source` to all vertices in `graph` with edge weights given @@ -69,6 +53,8 @@ pub struct DijkstraResult { /// of each vertex on some shortest path from `source` to that vertex. Returns `None` for any vertex /// not connected to `source`. /// +/// Time complexity is *O((|V| + |E|) log |V|)*, space complexity is *O(|V|)*. +/// /// # Panics /// /// Panics if `source` is not a valid vertex of `graph`. @@ -95,6 +81,7 @@ pub struct DijkstraResult { pub fn dijkstra(graph: &G, source: G::Vertex, weights: W) -> DijkstraResult where G: GraphTopology, + G::Vertex: Hash, W: Fn(G::Edge) -> u32, { let mut predecessors = graph.vertex_map(None); @@ -113,6 +100,8 @@ where /// by `weights` function. Returns the distances from `source` to each vertex. Returns `None` for any /// vertex not connected to `source`. /// +/// Time complexity is *O((|V| + |E|) log |V|)*, space complexity is *O(|V|)*. +/// /// # Panics /// /// Panics if `source` is not a valid vertex of `graph`. @@ -142,6 +131,7 @@ pub fn dijkstra_distances( ) -> EntityMap> where G: GraphTopology, + G::Vertex: Hash, W: Fn(G::Edge) -> u32, { dijkstra_impl(graph, source, weights, |_, _| {}) @@ -153,6 +143,8 @@ where /// the distances from `source` to each vertex, and the predecessors of each vertex on some shortest /// path from `source` to that vertex. Returns `None` for any vertex not connected to `source`. /// +/// Time complexity is *O((|V| + |E|) log |V|)*, space complexity is *O(|V|)*. +/// /// Prefer [`bfs`] for unweighted graphs, it has better time and space complexity. /// /// # Panics @@ -179,6 +171,7 @@ where pub fn dijkstra_unweighted(graph: &G, source: G::Vertex) -> DijkstraResult where G: GraphTopology, + G::Vertex: Hash, { dijkstra(graph, source, |_| 1) } @@ -189,6 +182,8 @@ where /// the distances from `source` to each vertex. Returns `None` for any vertex not connected to /// `source`. /// +/// Time complexity is *O((|V| + |E|) log |V|)*, space complexity is *O(|V|)*. +/// /// Prefer [`bfs_distances`] for unweighted graphs, it has better time and space complexity. /// /// # Panics @@ -217,6 +212,7 @@ pub fn dijkstra_distances_unweighted( ) -> EntityMap> where G: GraphTopology, + G::Vertex: Hash, { dijkstra_distances(graph, source, |_| 1) } @@ -229,32 +225,27 @@ fn dijkstra_impl( ) -> EntityMap> where G: GraphTopology, + G::Vertex: Hash, W: Fn(G::Edge) -> u32, F: FnMut(G::Vertex, G::Vertex), { let mut distances = graph.vertex_map(None); - let mut heap = BinaryHeap::new(); + let mut heap = PriorityQueue::new(); distances[source] = Some(0); - heap.push(DistanceOrderedVertex { - vertex: source, - distance: 0, - }); + heap.push(source, Reverse(0u32)); - while let Some(v) = heap.pop() { - for incidence in graph.incidences(v.vertex) { - let new_distance = distances[v.vertex].unwrap() + weights(incidence.1); + while let Some((v, Reverse(v_distance))) = heap.pop() { + for incidence in graph.incidences(v) { + let new_distance = v_distance + weights(incidence.1); if match distances[incidence.0] { None => true, Some(old_distance) if old_distance > new_distance => true, _ => false, } { distances[incidence.0] = Some(new_distance); - on_relax(incidence.0, v.vertex); - heap.push(DistanceOrderedVertex { - vertex: incidence.0, - distance: new_distance, - }); + on_relax(incidence.0, v); + heap.push_increase(incidence.0, Reverse(new_distance)); } } } diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index 471997d..bc0d76a 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -7,14 +7,14 @@ use crate::traits::{GraphTopology, IncidenceCursor}; /// /// Handles are stable for the lifetime of the graph. Obtain via graph methods like /// [`AppendGraph::add_vertex`] and [`AppendGraph::vertices`]. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub struct Vertex(usize); /// An opaque handle identifying an edge in an [`AppendGraph`]. /// /// Handles are stable for the lifetime of the graph. Obtain via graph methods like /// [`AppendGraph::add_edge`] and [`AppendGraph::edges`]. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub struct Edge(usize); /// An [`EntityMap`] for [`AppendGraph`] vertices.