Add algorithms::bfs_find_path
This commit is contained in:
+109
-3
@@ -425,10 +425,8 @@ where
|
||||
F: FnMut(G::Vertex, G::Vertex) -> bool,
|
||||
{
|
||||
let mut distances = graph.vertex_map(None);
|
||||
let mut queue = VecDeque::new();
|
||||
|
||||
distances[source] = Some(0);
|
||||
queue.push_back(source);
|
||||
let mut queue = VecDeque::from([source]);
|
||||
|
||||
while let Some(v) = queue.pop_front() {
|
||||
for neighbor in graph.adjacent_vertices(v) {
|
||||
@@ -451,6 +449,114 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// [Breadth-first search] from `source` for `target`, returns some shortest path as a sequence of
|
||||
/// edges.
|
||||
///
|
||||
/// Traverses vertices in `graph` starting from `source` until `target` is found, exploring all
|
||||
/// neighbors before descending further. Returns some shortest path from `source` to `target` as a
|
||||
/// sequence of edges in traversal order if `target` is a valid vertex of `graph` connected to
|
||||
/// `source`, or `None` otherwise. The returned path is empty if and only if `source` equals
|
||||
/// `target`.
|
||||
///
|
||||
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `source` is not a valid vertex of `graph`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use grapherity::prelude::*;
|
||||
/// # use grapherity::algorithms::bfs_find_path;
|
||||
/// # use grapherity::models::Graph;
|
||||
/// let mut graph = Graph::new();
|
||||
/// let source = graph.add_vertex();
|
||||
/// let target = graph.add_vertex();
|
||||
/// let e = graph.add_edge(source, target);
|
||||
///
|
||||
/// assert_eq!(bfs_find_path(&graph, source, target), Some(vec![e]));
|
||||
/// ```
|
||||
///
|
||||
/// [Breadth-first search]: https://en.wikipedia.org/wiki/Breadth-first_search
|
||||
pub fn bfs_find_path<G>(graph: &G, source: G::Vertex, target: G::Vertex) -> Option<Vec<G::Edge>>
|
||||
where
|
||||
G: GraphTopology,
|
||||
{
|
||||
bfs_find_path_where(graph, source, |v| v == target)
|
||||
}
|
||||
|
||||
/// [Breadth-first search] from `source` for a vertex matching `predicate`, returns some shortest
|
||||
/// path as a sequence of edges.
|
||||
///
|
||||
/// Traverses vertices in `graph` starting from `source` until a vertex satisfying `predicate` is
|
||||
/// found, exploring all neighbors before descending further. Returns some shortest path from
|
||||
/// `source` to the first vertex satisfying `predicate` as a sequence of edges in traversal order,
|
||||
/// or `None` if no such vertex is connected to `source`. The returned path is empty if and only if
|
||||
/// `source` satisfies `predicate`.
|
||||
///
|
||||
/// Time complexity is *O(|V| + |E|)*, space complexity is *O(|V|)*.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `source` is not a valid vertex of `graph`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use grapherity::prelude::*;
|
||||
/// # use grapherity::algorithms::bfs_find_path_where;
|
||||
/// # use grapherity::models::Graph;
|
||||
/// let mut graph = Graph::new();
|
||||
/// let source = graph.add_vertex();
|
||||
/// let target = graph.add_vertex();
|
||||
/// let e = graph.add_edge(source, target);
|
||||
///
|
||||
/// assert_eq!(bfs_find_path_where(&graph, source, |v| v == target), Some(vec![e]));
|
||||
/// ```
|
||||
///
|
||||
/// [Breadth-first search]: https://en.wikipedia.org/wiki/Breadth-first_search
|
||||
pub fn bfs_find_path_where<G, P>(graph: &G, source: G::Vertex, predicate: P) -> Option<Vec<G::Edge>>
|
||||
where
|
||||
G: GraphTopology,
|
||||
P: Fn(G::Vertex) -> bool,
|
||||
{
|
||||
if predicate(source) {
|
||||
return Some(vec![]);
|
||||
}
|
||||
|
||||
let mut visited = graph.vertex_map(false);
|
||||
visited[source] = true;
|
||||
let mut predecessor: ElementMap<G::Vertex, Option<(G::Vertex, G::Edge)>> =
|
||||
graph.vertex_map(None);
|
||||
let mut queue = VecDeque::from([source]);
|
||||
|
||||
while let Some(v) = queue.pop_front() {
|
||||
for Incidence {
|
||||
vertex: neighbor,
|
||||
edge,
|
||||
} in graph.incidences(v)
|
||||
{
|
||||
if !visited[neighbor] {
|
||||
visited[neighbor] = true;
|
||||
predecessor[neighbor] = Some((v, edge));
|
||||
if predicate(neighbor) {
|
||||
let mut path = vec![edge];
|
||||
let mut current = v;
|
||||
while let Some((prev, e)) = predecessor[current] {
|
||||
path.push(e);
|
||||
current = prev;
|
||||
}
|
||||
path.reverse();
|
||||
return Some(path);
|
||||
}
|
||||
queue.push_back(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
// TODO: 'visited' is already encoded in 'predecessors', except for 'source', which is visited, but has no predecessor.
|
||||
/// Return data type for [`dfs`].
|
||||
pub struct DfsResult<V: Copy> {
|
||||
|
||||
+114
@@ -20,6 +20,14 @@ macro_rules! bfs_tests {
|
||||
bfs_find_where_disconnected,
|
||||
bfs_find_where_no_match,
|
||||
bfs_find_where_nearest,
|
||||
bfs_find_path_source_equals_target,
|
||||
bfs_find_path_disconnected,
|
||||
bfs_find_path_adjacent,
|
||||
bfs_find_path,
|
||||
bfs_find_path_where_source_matches,
|
||||
bfs_find_path_where_disconnected,
|
||||
bfs_find_path_where_no_match,
|
||||
bfs_find_path_where,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -227,3 +235,109 @@ where
|
||||
vertices[8]
|
||||
);
|
||||
}
|
||||
|
||||
fn bfs_find_path_source_equals_target<G: MakeTestGraph>()
|
||||
where
|
||||
G::Edge: Debug,
|
||||
{
|
||||
let (graph, v) = G::single_vertex();
|
||||
assert_eq!(
|
||||
algorithms::bfs_find_path(&graph, v, v),
|
||||
Some(vec![]),
|
||||
"path from source to itself should be empty"
|
||||
);
|
||||
}
|
||||
|
||||
fn bfs_find_path_disconnected<G: MakeTestGraph>()
|
||||
where
|
||||
G::Edge: Debug,
|
||||
{
|
||||
let (graph, vertices) = G::disconnected();
|
||||
assert_eq!(
|
||||
algorithms::bfs_find_path(&graph, vertices[0], vertices[1]),
|
||||
None,
|
||||
"no path should exist to disconnected vertex"
|
||||
);
|
||||
}
|
||||
|
||||
fn bfs_find_path_adjacent<G: MakeTestGraph>()
|
||||
where
|
||||
G::Edge: Debug,
|
||||
{
|
||||
let (graph, vertices, e) = G::single_edge();
|
||||
let path = algorithms::bfs_find_path(&graph, vertices[0], vertices[1])
|
||||
.expect("path should exist between adjacent vertices");
|
||||
assert_eq!(path, [e], "path should contain only the connecting edge");
|
||||
}
|
||||
|
||||
fn bfs_find_path<G: MakeTestGraph>()
|
||||
where
|
||||
G::Vertex: Debug,
|
||||
G::Edge: Debug,
|
||||
{
|
||||
let (graph, vertices, _, _) = G::standard();
|
||||
fixtures::assert_standard_unweighted_distances_v0(
|
||||
|v| {
|
||||
let result = algorithms::bfs_find_path(&graph, vertices[0], v);
|
||||
let path = fixtures::assert_valid_path(&graph, result, vertices[0], &[v]);
|
||||
Some(u32::try_from(path.len()).unwrap())
|
||||
},
|
||||
&vertices,
|
||||
);
|
||||
}
|
||||
|
||||
fn bfs_find_path_where_source_matches<G: MakeTestGraph>()
|
||||
where
|
||||
G::Edge: Debug,
|
||||
{
|
||||
let (graph, v) = G::single_vertex();
|
||||
assert_eq!(
|
||||
algorithms::bfs_find_path_where(&graph, v, |u| u == v),
|
||||
Some(vec![]),
|
||||
"path from source to itself should be empty"
|
||||
);
|
||||
}
|
||||
|
||||
fn bfs_find_path_where_disconnected<G: MakeTestGraph>()
|
||||
where
|
||||
G::Edge: Debug,
|
||||
{
|
||||
let (graph, vertices) = G::disconnected();
|
||||
assert_eq!(
|
||||
algorithms::bfs_find_path_where(&graph, vertices[0], |v| v == vertices[1]),
|
||||
None,
|
||||
"no path should exist to disconnected vertex"
|
||||
);
|
||||
}
|
||||
|
||||
fn bfs_find_path_where_no_match<G: MakeTestGraph>()
|
||||
where
|
||||
G::Edge: Debug,
|
||||
{
|
||||
let (graph, vertices, _, _) = G::standard();
|
||||
assert_eq!(
|
||||
algorithms::bfs_find_path_where(&graph, vertices[0], |_| false),
|
||||
None,
|
||||
"no path should exist when predicate never matches"
|
||||
);
|
||||
}
|
||||
|
||||
fn bfs_find_path_where<G: MakeTestGraph>()
|
||||
where
|
||||
G::Vertex: Debug,
|
||||
G::Edge: Debug,
|
||||
{
|
||||
let (graph, vertices, _, _) = G::standard();
|
||||
// vertices[7] and vertices[8] are at distance 3 from vertices[0]; vertices[9] is at distance 4.
|
||||
// BFS must return one shortest path, so it must lead to one of the distance-3 matches.
|
||||
let result = algorithms::bfs_find_path_where(&graph, vertices[0], |v| {
|
||||
v == vertices[7] || v == vertices[8] || v == vertices[9]
|
||||
});
|
||||
let path =
|
||||
fixtures::assert_valid_path(&graph, result, vertices[0], &[vertices[7], vertices[8]]);
|
||||
assert_eq!(
|
||||
path.len(),
|
||||
3,
|
||||
"BFS should return shortest path to a matching vertex"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user