Update Dijkstra's algorithm to take any weights that satisfy the new Weight trait
This commit is contained in:
+46
-15
@@ -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<Output = Self> + Default {}
|
||||
|
||||
impl<T: Copy + Ord + Add<Output = T> + Default> Weight for T {}
|
||||
|
||||
/// 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.
|
||||
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.
|
||||
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.
|
||||
/// [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
|
||||
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
|
||||
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<G, W>(
|
||||
graph: &G,
|
||||
source: G::Vertex,
|
||||
weights: W,
|
||||
) -> ElementMap<G::Vertex, Option<u32>>
|
||||
weights: impl Fn(G::Edge) -> W,
|
||||
) -> ElementMap<G::Vertex, Option<W>>
|
||||
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<G>(graph: &G, source: G::Vertex) -> DijkstraResult<G::Vertex>
|
||||
pub fn dijkstra_unweighted<G>(graph: &G, source: G::Vertex) -> DijkstraResult<G::Vertex, u32>
|
||||
where
|
||||
G: GraphTopology,
|
||||
G::Vertex: Hash,
|
||||
@@ -220,24 +251,24 @@ where
|
||||
fn dijkstra_impl<G, W, F>(
|
||||
graph: &G,
|
||||
source: G::Vertex,
|
||||
weights: W,
|
||||
weights: impl Fn(G::Edge) -> W,
|
||||
mut on_relax: F,
|
||||
) -> ElementMap<G::Vertex, Option<u32>>
|
||||
) -> ElementMap<G::Vertex, Option<W>>
|
||||
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,
|
||||
|
||||
+5
-3
@@ -173,7 +173,7 @@ where
|
||||
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
|
||||
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])
|
||||
where
|
||||
fn assert_disconnected<G: GraphTopology>(
|
||||
result: &DijkstraResult<G::Vertex, u32>,
|
||||
vertices: &[G::Vertex],
|
||||
) where
|
||||
G::Vertex: Debug,
|
||||
{
|
||||
assert_distances_disconnected::<G>(&result.distances, &vertices);
|
||||
|
||||
Reference in New Issue
Block a user