use crate::node::NodeIndex;
use crate::plugins::algorithm::{
AlgorithmData, AlgorithmResult, GraphAlgorithm, PluginContext, PluginInfo,
};
use crate::vgi::{Capability, GraphType, VgiResult, VirtualGraph};
use std::any::Any;
use std::collections::{HashMap, VecDeque};
#[inline]
fn node_to_idx(node: NodeIndex) -> usize {
node.index()
}
#[derive(Debug, Clone)]
pub struct BfsResult {
pub visited_count: usize,
pub order: Vec<usize>,
pub distances: HashMap<usize, usize>,
pub parent: HashMap<usize, usize>,
}
pub struct BfsPlugin {
start_node: usize,
record_order: bool,
}
impl BfsPlugin {
pub fn new(start_node: usize) -> Self {
Self {
start_node,
record_order: true,
}
}
pub fn new_unspecified() -> Self {
Self {
start_node: 0,
record_order: true,
}
}
pub fn with_record_order(mut self, record: bool) -> Self {
self.record_order = record;
self
}
pub fn traverse<G>(&self, graph: &G, start: usize) -> VgiResult<BfsResult>
where
G: VirtualGraph + ?Sized,
{
let n = graph.node_count();
if n == 0 {
return Ok(BfsResult {
visited_count: 0,
order: vec![],
distances: HashMap::new(),
parent: HashMap::new(),
});
}
let node_indices: Vec<NodeIndex> = graph.nodes().map(|n| n.index()).collect();
if start >= node_indices.len() {
return Err(crate::vgi::VgiError::Internal {
message: format!("Start node {} out of range", start),
});
}
let start_idx = node_indices[start];
let mut visited = vec![None; n];
let mut order: Vec<usize> = Vec::with_capacity(n);
let mut parent: Vec<Option<usize>> = vec![None; n];
let mut queue = VecDeque::with_capacity(n);
queue.push_back(start_idx);
visited[start] = Some(0);
while let Some(node_idx) = queue.pop_front() {
let node_usize = node_to_idx(node_idx);
if self.record_order {
order.push(node_usize);
}
let current_dist = visited[node_usize].unwrap();
for neighbor_idx in graph.neighbors(node_idx) {
let neighbor_usize = node_to_idx(neighbor_idx);
if visited[neighbor_usize].is_none() {
visited[neighbor_usize] = Some(current_dist + 1);
parent[neighbor_usize] = Some(node_usize);
queue.push_back(neighbor_idx);
}
}
}
let visited_count = visited.iter().filter(|x| x.is_some()).count();
let mut distances = HashMap::with_capacity(visited_count);
for (i, dist) in visited.iter().enumerate() {
if let Some(d) = dist {
distances.insert(i, *d);
}
}
let mut parent_map = HashMap::with_capacity(visited_count);
for (i, p) in parent.iter().enumerate() {
if let Some(parent_node) = p {
parent_map.insert(i, *parent_node);
}
}
Ok(BfsResult {
visited_count: distances.len(),
order,
distances,
parent: parent_map,
})
}
pub fn shortest_path<G>(&self, graph: &G, target: usize) -> VgiResult<Vec<usize>>
where
G: VirtualGraph + ?Sized,
{
let n = graph.node_count();
let node_indices: Vec<NodeIndex> = graph.nodes().map(|n| n.index()).collect();
if self.start_node >= node_indices.len() || target >= node_indices.len() {
return Err(crate::vgi::VgiError::Internal {
message: "Node index out of range".to_string(),
});
}
let start_idx = node_indices[self.start_node];
let mut visited = vec![false; n];
let mut parent: Vec<Option<usize>> = vec![None; n];
let mut queue = VecDeque::with_capacity(n);
queue.push_back(start_idx);
visited[self.start_node] = true;
while let Some(node_idx) = queue.pop_front() {
let node_usize = node_to_idx(node_idx);
if node_usize == target {
break;
}
for neighbor_idx in graph.neighbors(node_idx) {
let neighbor_usize = node_to_idx(neighbor_idx);
if !visited[neighbor_usize] {
visited[neighbor_usize] = true;
parent[neighbor_usize] = Some(node_usize);
queue.push_back(neighbor_idx);
}
}
}
if !visited[target] {
return Err(crate::vgi::VgiError::Internal {
message: format!("No path from {} to {}", self.start_node, target),
});
}
let mut path = Vec::new();
let mut current = target;
loop {
path.push(current);
if current == self.start_node {
break;
}
current = parent[current].unwrap_or(self.start_node);
}
path.reverse();
Ok(path)
}
}
impl GraphAlgorithm for BfsPlugin {
fn info(&self) -> PluginInfo {
PluginInfo::new("bfs", "1.0.0", "广度优先搜索算法")
.with_author("God-Graph Team")
.with_required_capabilities(&[Capability::IncrementalUpdate])
.with_supported_graph_types(&[GraphType::Directed, GraphType::Undirected])
.with_tags(&["traversal", "search", "shortest-path"])
}
fn execute<G>(&self, ctx: &mut PluginContext<G>) -> VgiResult<AlgorithmResult>
where
G: VirtualGraph + ?Sized,
{
let start = self.start_node;
if start >= ctx.graph.node_count() {
return Err(crate::vgi::VgiError::Internal {
message: format!("Start node {} out of range", start),
});
}
ctx.report_progress(0.1);
let result = self.traverse(ctx.graph, start)?;
ctx.report_progress(1.0);
Ok(
AlgorithmResult::new("bfs", AlgorithmData::NodeList(result.order))
.with_metadata("start_node", start.to_string())
.with_metadata("visited_count", result.visited_count.to_string()),
)
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::Graph;
use crate::graph::traits::GraphOps;
#[test]
fn test_bfs_basic() {
let mut graph = Graph::<String, f64>::directed();
let n0 = graph.add_node("node_0".to_string()).unwrap();
let n1 = graph.add_node("node_1".to_string()).unwrap();
let n2 = graph.add_node("node_2".to_string()).unwrap();
let n3 = graph.add_node("node_3".to_string()).unwrap();
graph.add_edge(n0, n1, 1.0).unwrap();
graph.add_edge(n0, n2, 1.0).unwrap();
graph.add_edge(n1, n3, 1.0).unwrap();
let plugin = BfsPlugin::new(0);
let result = plugin.traverse(&graph, 0).unwrap();
assert_eq!(result.visited_count, 4);
assert_eq!(result.order.len(), 4);
assert_eq!(result.order[0], 0);
assert_eq!(result.distances[&0], 0);
assert_eq!(result.distances[&1], 1);
assert_eq!(result.distances[&2], 1);
assert_eq!(result.distances[&3], 2);
}
#[test]
fn test_bfs_plugin_info() {
let plugin = BfsPlugin::new(0);
let info = plugin.info();
assert_eq!(info.name, "bfs");
assert!(info.tags.contains(&"traversal".to_string()));
}
}