Add algorithms::bfs_find_path
This commit is contained in:
+109
-3
@@ -425,10 +425,8 @@ where
|
|||||||
F: FnMut(G::Vertex, G::Vertex) -> bool,
|
F: FnMut(G::Vertex, G::Vertex) -> bool,
|
||||||
{
|
{
|
||||||
let mut distances = graph.vertex_map(None);
|
let mut distances = graph.vertex_map(None);
|
||||||
let mut queue = VecDeque::new();
|
|
||||||
|
|
||||||
distances[source] = Some(0);
|
distances[source] = Some(0);
|
||||||
queue.push_back(source);
|
let mut queue = VecDeque::from([source]);
|
||||||
|
|
||||||
while let Some(v) = queue.pop_front() {
|
while let Some(v) = queue.pop_front() {
|
||||||
for neighbor in graph.adjacent_vertices(v) {
|
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.
|
// TODO: 'visited' is already encoded in 'predecessors', except for 'source', which is visited, but has no predecessor.
|
||||||
/// Return data type for [`dfs`].
|
/// Return data type for [`dfs`].
|
||||||
pub struct DfsResult<V: Copy> {
|
pub struct DfsResult<V: Copy> {
|
||||||
|
|||||||
+114
@@ -20,6 +20,14 @@ macro_rules! bfs_tests {
|
|||||||
bfs_find_where_disconnected,
|
bfs_find_where_disconnected,
|
||||||
bfs_find_where_no_match,
|
bfs_find_where_no_match,
|
||||||
bfs_find_where_nearest,
|
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]
|
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