scirs2_graph/algorithms/
traversal.rs

1//! Graph traversal algorithms
2//!
3//! This module provides breadth-first search (BFS) and depth-first search (DFS)
4//! algorithms for both directed and undirected graphs.
5
6use std::collections::{BinaryHeap, HashSet, VecDeque};
7
8use crate::base::{DiGraph, EdgeWeight, Graph, Node};
9use crate::error::{GraphError, Result};
10
11/// Performs breadth-first search (BFS) from a given starting node
12///
13/// # Arguments
14/// * `graph` - The graph to traverse
15/// * `source` - The source node
16///
17/// # Returns
18/// * `Result<Vec<N>>` - The nodes visited in BFS order
19///
20/// # Time Complexity
21/// O(V + E) where V is the number of vertices and E is the number of edges.
22/// Each vertex and edge is visited at most once.
23///
24/// # Space Complexity
25/// O(V) for the visited set and queue.
26#[allow(dead_code)]
27pub fn breadth_first_search<N, E, Ix>(graph: &Graph<N, E, Ix>, source: &N) -> Result<Vec<N>>
28where
29    N: Node + std::fmt::Debug,
30    E: EdgeWeight,
31    Ix: petgraph::graph::IndexType,
32{
33    if !graph.has_node(source) {
34        return Err(GraphError::InvalidGraph(format!(
35            "Source node {source:?} not found"
36        )));
37    }
38
39    let mut visited = HashSet::new();
40    let mut queue = VecDeque::new();
41    let mut result = Vec::new();
42
43    // Find the starting node index
44    let start_idx = graph
45        .inner()
46        .node_indices()
47        .find(|&idx| graph.inner()[idx] == *source)
48        .unwrap();
49
50    queue.push_back(start_idx);
51    visited.insert(start_idx);
52
53    while let Some(current_idx) = queue.pop_front() {
54        result.push(graph.inner()[current_idx].clone());
55
56        // Visit all unvisited neighbors
57        for neighbor_idx in graph.inner().neighbors(current_idx) {
58            if !visited.contains(&neighbor_idx) {
59                visited.insert(neighbor_idx);
60                queue.push_back(neighbor_idx);
61            }
62        }
63    }
64
65    Ok(result)
66}
67
68/// Performs breadth-first search (BFS) from a given starting node in a directed graph
69///
70/// # Arguments
71/// * `graph` - The directed graph to traverse
72/// * `source` - The source node
73///
74/// # Returns
75/// * `Result<Vec<N>>` - The nodes visited in BFS order
76///
77/// # Time Complexity
78/// O(V + E) where V is the number of vertices and E is the number of edges.
79/// Each vertex and edge is visited at most once.
80///
81/// # Space Complexity
82/// O(V) for the visited set and queue.
83#[allow(dead_code)]
84pub fn breadth_first_search_digraph<N, E, Ix>(
85    graph: &DiGraph<N, E, Ix>,
86    source: &N,
87) -> Result<Vec<N>>
88where
89    N: Node + std::fmt::Debug,
90    E: EdgeWeight,
91    Ix: petgraph::graph::IndexType,
92{
93    if !graph.has_node(source) {
94        return Err(GraphError::InvalidGraph(format!(
95            "Source node {source:?} not found"
96        )));
97    }
98
99    let mut visited = HashSet::new();
100    let mut queue = VecDeque::new();
101    let mut result = Vec::new();
102
103    // Find the starting node index
104    let start_idx = graph
105        .inner()
106        .node_indices()
107        .find(|&idx| graph.inner()[idx] == *source)
108        .unwrap();
109
110    queue.push_back(start_idx);
111    visited.insert(start_idx);
112
113    while let Some(current_idx) = queue.pop_front() {
114        result.push(graph.inner()[current_idx].clone());
115
116        // Visit all unvisited neighbors (outgoing edges only for directed graph)
117        for neighbor_idx in graph
118            .inner()
119            .neighbors_directed(current_idx, petgraph::Direction::Outgoing)
120        {
121            if !visited.contains(&neighbor_idx) {
122                visited.insert(neighbor_idx);
123                queue.push_back(neighbor_idx);
124            }
125        }
126    }
127
128    Ok(result)
129}
130
131/// Performs depth-first search (DFS) from a given starting node
132///
133/// # Arguments
134/// * `graph` - The graph to traverse
135/// * `source` - The source node
136///
137/// # Returns
138/// * `Result<Vec<N>>` - The nodes visited in DFS order
139///
140/// # Time Complexity
141/// O(V + E) where V is the number of vertices and E is the number of edges.
142/// Each vertex and edge is visited at most once.
143///
144/// # Space Complexity
145/// O(V) for the visited set and stack. In the worst case, the stack
146/// can contain all vertices (e.g., in a linear graph).
147#[allow(dead_code)]
148pub fn depth_first_search<N, E, Ix>(graph: &Graph<N, E, Ix>, source: &N) -> Result<Vec<N>>
149where
150    N: Node + std::fmt::Debug,
151    E: EdgeWeight,
152    Ix: petgraph::graph::IndexType,
153{
154    if !graph.has_node(source) {
155        return Err(GraphError::InvalidGraph(format!(
156            "Source node {source:?} not found"
157        )));
158    }
159
160    let mut visited = HashSet::new();
161    let mut stack = Vec::new();
162    let mut result = Vec::new();
163
164    // Find the starting node index
165    let start_idx = graph
166        .inner()
167        .node_indices()
168        .find(|&idx| graph.inner()[idx] == *source)
169        .unwrap();
170
171    stack.push(start_idx);
172
173    while let Some(current_idx) = stack.pop() {
174        if !visited.contains(&current_idx) {
175            visited.insert(current_idx);
176            result.push(graph.inner()[current_idx].clone());
177
178            // Add all unvisited neighbors to the stack (in reverse order for consistent traversal)
179            let mut neighbors: Vec<_> = graph.inner().neighbors(current_idx).collect();
180            neighbors.reverse();
181            for neighbor_idx in neighbors {
182                if !visited.contains(&neighbor_idx) {
183                    stack.push(neighbor_idx);
184                }
185            }
186        }
187    }
188
189    Ok(result)
190}
191
192/// Performs depth-first search (DFS) from a given starting node in a directed graph
193///
194/// # Arguments
195/// * `graph` - The directed graph to traverse
196/// * `source` - The source node
197///
198/// # Returns
199/// * `Result<Vec<N>>` - The nodes visited in DFS order
200#[allow(dead_code)]
201pub fn depth_first_search_digraph<N, E, Ix>(graph: &DiGraph<N, E, Ix>, source: &N) -> Result<Vec<N>>
202where
203    N: Node + std::fmt::Debug,
204    E: EdgeWeight,
205    Ix: petgraph::graph::IndexType,
206{
207    if !graph.has_node(source) {
208        return Err(GraphError::InvalidGraph(format!(
209            "Source node {source:?} not found"
210        )));
211    }
212
213    let mut visited = HashSet::new();
214    let mut stack = Vec::new();
215    let mut result = Vec::new();
216
217    // Find the starting node index
218    let start_idx = graph
219        .inner()
220        .node_indices()
221        .find(|&idx| graph.inner()[idx] == *source)
222        .unwrap();
223
224    stack.push(start_idx);
225
226    while let Some(current_idx) = stack.pop() {
227        if !visited.contains(&current_idx) {
228            visited.insert(current_idx);
229            result.push(graph.inner()[current_idx].clone());
230
231            // Add all unvisited neighbors to the stack (outgoing edges only for directed graph)
232            let mut neighbors: Vec<_> = graph
233                .inner()
234                .neighbors_directed(current_idx, petgraph::Direction::Outgoing)
235                .collect();
236            neighbors.reverse();
237            for neighbor_idx in neighbors {
238                if !visited.contains(&neighbor_idx) {
239                    stack.push(neighbor_idx);
240                }
241            }
242        }
243    }
244
245    Ok(result)
246}
247
248/// Priority-first search state for priority queue
249#[derive(Clone)]
250struct PriorityState<N: Node + std::fmt::Debug, P: PartialOrd> {
251    node: N,
252    priority: P,
253}
254
255impl<N: Node + std::fmt::Debug, P: PartialOrd> PartialEq for PriorityState<N, P> {
256    fn eq(&self, other: &Self) -> bool {
257        self.node == other.node
258    }
259}
260
261impl<N: Node + std::fmt::Debug, P: PartialOrd> Eq for PriorityState<N, P> {}
262
263impl<N: Node + std::fmt::Debug, P: PartialOrd> PartialOrd for PriorityState<N, P> {
264    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
265        Some(self.cmp(other))
266    }
267}
268
269impl<N: Node + std::fmt::Debug, P: PartialOrd> Ord for PriorityState<N, P> {
270    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
271        // Reverse order for min-heap behavior
272        other
273            .priority
274            .partial_cmp(&self.priority)
275            .unwrap_or(std::cmp::Ordering::Equal)
276    }
277}
278
279/// Performs priority-first search from a given starting node
280///
281/// This is a generalized graph traversal algorithm where nodes are visited
282/// in order of a priority function. When the priority function represents
283/// distance, this becomes Dijkstra's algorithm. When all priorities are equal,
284/// this becomes breadth-first search.
285///
286/// # Arguments
287/// * `graph` - The graph to traverse
288/// * `source` - The source node
289/// * `priority_fn` - Function that assigns priority to each node (lower values visited first)
290///
291/// # Returns
292/// * `Result<Vec<N>>` - The nodes visited in priority order
293#[allow(dead_code)]
294pub fn priority_first_search<N, E, Ix, P, F>(
295    graph: &Graph<N, E, Ix>,
296    source: &N,
297    priority_fn: F,
298) -> Result<Vec<N>>
299where
300    N: Node + std::fmt::Debug + Clone,
301    E: EdgeWeight,
302    Ix: petgraph::graph::IndexType,
303    P: PartialOrd + Clone + Copy,
304    F: Fn(&N) -> P,
305{
306    if !graph.has_node(source) {
307        return Err(GraphError::InvalidGraph(format!(
308            "Source node {source:?} not found"
309        )));
310    }
311
312    let mut visited = HashSet::new();
313    let mut priority_queue = BinaryHeap::new();
314    let mut result = Vec::new();
315
316    // Find the starting node index (not used but kept for consistency)
317    let _start_idx = graph
318        .inner()
319        .node_indices()
320        .find(|&idx| graph.inner()[idx] == *source)
321        .unwrap();
322
323    priority_queue.push(PriorityState {
324        node: source.clone(),
325        priority: priority_fn(source),
326    });
327
328    while let Some(current_state) = priority_queue.pop() {
329        let current_node = &current_state.node;
330
331        if visited.contains(current_node) {
332            continue;
333        }
334
335        visited.insert(current_node.clone());
336        result.push(current_node.clone());
337
338        // Find current node index
339        let current_idx = graph
340            .inner()
341            .node_indices()
342            .find(|&idx| graph.inner()[idx] == *current_node)
343            .unwrap();
344
345        // Add all unvisited neighbors to the priority queue
346        for neighbor_idx in graph.inner().neighbors(current_idx) {
347            let neighbor_node = &graph.inner()[neighbor_idx];
348            if !visited.contains(neighbor_node) {
349                priority_queue.push(PriorityState {
350                    node: neighbor_node.clone(),
351                    priority: priority_fn(neighbor_node),
352                });
353            }
354        }
355    }
356
357    Ok(result)
358}
359
360/// Performs priority-first search from a given starting node in a directed graph
361///
362/// # Arguments
363/// * `graph` - The directed graph to traverse
364/// * `source` - The source node
365/// * `priority_fn` - Function that assigns priority to each node (lower values visited first)
366///
367/// # Returns
368/// * `Result<Vec<N>>` - The nodes visited in priority order
369#[allow(dead_code)]
370pub fn priority_first_search_digraph<N, E, Ix, P, F>(
371    graph: &DiGraph<N, E, Ix>,
372    source: &N,
373    priority_fn: F,
374) -> Result<Vec<N>>
375where
376    N: Node + std::fmt::Debug + Clone,
377    E: EdgeWeight,
378    Ix: petgraph::graph::IndexType,
379    P: PartialOrd + Clone + Copy,
380    F: Fn(&N) -> P,
381{
382    if !graph.has_node(source) {
383        return Err(GraphError::InvalidGraph(format!(
384            "Source node {source:?} not found"
385        )));
386    }
387
388    let mut visited = HashSet::new();
389    let mut priority_queue = BinaryHeap::new();
390    let mut result = Vec::new();
391
392    priority_queue.push(PriorityState {
393        node: source.clone(),
394        priority: priority_fn(source),
395    });
396
397    while let Some(current_state) = priority_queue.pop() {
398        let current_node = &current_state.node;
399
400        if visited.contains(current_node) {
401            continue;
402        }
403
404        visited.insert(current_node.clone());
405        result.push(current_node.clone());
406
407        // Find current node index
408        let current_idx = graph
409            .inner()
410            .node_indices()
411            .find(|&idx| graph.inner()[idx] == *current_node)
412            .unwrap();
413
414        // Add all unvisited successors to the priority queue (directed graph)
415        for neighbor_idx in graph
416            .inner()
417            .neighbors_directed(current_idx, petgraph::Direction::Outgoing)
418        {
419            let neighbor_node = &graph.inner()[neighbor_idx];
420            if !visited.contains(neighbor_node) {
421                priority_queue.push(PriorityState {
422                    node: neighbor_node.clone(),
423                    priority: priority_fn(neighbor_node),
424                });
425            }
426        }
427    }
428
429    Ok(result)
430}
431
432/// Performs bidirectional breadth-first search to find a path between two nodes
433///
434/// This algorithm searches from both the start and goal nodes simultaneously,
435/// which can be more efficient than unidirectional search for finding paths
436/// in large graphs.
437///
438/// # Arguments
439/// * `graph` - The graph to search
440/// * `source` - The source node
441/// * `goal` - The goal node
442///
443/// # Returns
444/// * `Result<Option<Vec<N>>>` - The path from start to goal, or None if no path exists
445#[allow(dead_code)]
446pub fn bidirectional_search<N, E, Ix>(
447    graph: &Graph<N, E, Ix>,
448    source: &N,
449    goal: &N,
450) -> Result<Option<Vec<N>>>
451where
452    N: Node + std::fmt::Debug + Clone + std::hash::Hash + Eq,
453    E: EdgeWeight,
454    Ix: petgraph::graph::IndexType,
455{
456    if !graph.has_node(source) || !graph.has_node(goal) {
457        return Err(GraphError::InvalidGraph(
458            "Source or goal node not found".to_string(),
459        ));
460    }
461
462    if source == goal {
463        return Ok(Some(vec![source.clone()]));
464    }
465
466    // Find node indices
467    let start_idx = graph
468        .inner()
469        .node_indices()
470        .find(|&idx| graph.inner()[idx] == *source)
471        .unwrap();
472    let goal_idx = graph
473        .inner()
474        .node_indices()
475        .find(|&idx| graph.inner()[idx] == *goal)
476        .unwrap();
477
478    let mut forward_queue = VecDeque::new();
479    let mut backward_queue = VecDeque::new();
480    let mut forward_visited = HashSet::new();
481    let mut backward_visited = HashSet::new();
482    let mut forward_parent: std::collections::HashMap<
483        petgraph::graph::NodeIndex<Ix>,
484        petgraph::graph::NodeIndex<Ix>,
485    > = std::collections::HashMap::new();
486    let mut backward_parent: std::collections::HashMap<
487        petgraph::graph::NodeIndex<Ix>,
488        petgraph::graph::NodeIndex<Ix>,
489    > = std::collections::HashMap::new();
490
491    forward_queue.push_back(start_idx);
492    backward_queue.push_back(goal_idx);
493    forward_visited.insert(start_idx);
494    backward_visited.insert(goal_idx);
495
496    while !forward_queue.is_empty() || !backward_queue.is_empty() {
497        // Forward search step
498        if !forward_queue.is_empty() {
499            let current = forward_queue.pop_front().unwrap();
500
501            // Check if we've met the backward search
502            if backward_visited.contains(&current) {
503                return Ok(Some(reconstruct_bidirectional_path(
504                    graph,
505                    start_idx,
506                    goal_idx,
507                    current,
508                    &forward_parent,
509                    &backward_parent,
510                )));
511            }
512
513            for neighbor in graph.inner().neighbors(current) {
514                if !forward_visited.contains(&neighbor) {
515                    forward_visited.insert(neighbor);
516                    forward_parent.insert(neighbor, current);
517                    forward_queue.push_back(neighbor);
518                }
519            }
520        }
521
522        // Backward search step
523        if !backward_queue.is_empty() {
524            let current = backward_queue.pop_front().unwrap();
525
526            // Check if we've met the forward search
527            if forward_visited.contains(&current) {
528                return Ok(Some(reconstruct_bidirectional_path(
529                    graph,
530                    start_idx,
531                    goal_idx,
532                    current,
533                    &forward_parent,
534                    &backward_parent,
535                )));
536            }
537
538            for neighbor in graph.inner().neighbors(current) {
539                if !backward_visited.contains(&neighbor) {
540                    backward_visited.insert(neighbor);
541                    backward_parent.insert(neighbor, current);
542                    backward_queue.push_back(neighbor);
543                }
544            }
545        }
546    }
547
548    Ok(None)
549}
550
551/// Bidirectional search for directed graphs
552#[allow(dead_code)]
553pub fn bidirectional_search_digraph<N, E, Ix>(
554    graph: &DiGraph<N, E, Ix>,
555    source: &N,
556    goal: &N,
557) -> Result<Option<Vec<N>>>
558where
559    N: Node + std::fmt::Debug + Clone + std::hash::Hash + Eq,
560    E: EdgeWeight,
561    Ix: petgraph::graph::IndexType,
562{
563    if !graph.has_node(source) || !graph.has_node(goal) {
564        return Err(GraphError::InvalidGraph(
565            "Source or goal node not found".to_string(),
566        ));
567    }
568
569    if source == goal {
570        return Ok(Some(vec![source.clone()]));
571    }
572
573    // Find node indices
574    let start_idx = graph
575        .inner()
576        .node_indices()
577        .find(|&idx| graph.inner()[idx] == *source)
578        .unwrap();
579    let goal_idx = graph
580        .inner()
581        .node_indices()
582        .find(|&idx| graph.inner()[idx] == *goal)
583        .unwrap();
584
585    let mut forward_queue = VecDeque::new();
586    let mut backward_queue = VecDeque::new();
587    let mut forward_visited = HashSet::new();
588    let mut backward_visited = HashSet::new();
589    let mut forward_parent: std::collections::HashMap<
590        petgraph::graph::NodeIndex<Ix>,
591        petgraph::graph::NodeIndex<Ix>,
592    > = std::collections::HashMap::new();
593    let mut backward_parent: std::collections::HashMap<
594        petgraph::graph::NodeIndex<Ix>,
595        petgraph::graph::NodeIndex<Ix>,
596    > = std::collections::HashMap::new();
597
598    forward_queue.push_back(start_idx);
599    backward_queue.push_back(goal_idx);
600    forward_visited.insert(start_idx);
601    backward_visited.insert(goal_idx);
602
603    while !forward_queue.is_empty() || !backward_queue.is_empty() {
604        // Forward search step (follow outgoing edges)
605        if !forward_queue.is_empty() {
606            let current = forward_queue.pop_front().unwrap();
607
608            // Check if we've met the backward search
609            if backward_visited.contains(&current) {
610                return Ok(Some(reconstruct_bidirectional_path_digraph(
611                    graph,
612                    start_idx,
613                    goal_idx,
614                    current,
615                    &forward_parent,
616                    &backward_parent,
617                )));
618            }
619
620            for neighbor in graph
621                .inner()
622                .neighbors_directed(current, petgraph::Direction::Outgoing)
623            {
624                if !forward_visited.contains(&neighbor) {
625                    forward_visited.insert(neighbor);
626                    forward_parent.insert(neighbor, current);
627                    forward_queue.push_back(neighbor);
628                }
629            }
630        }
631
632        // Backward search step (follow incoming edges)
633        if !backward_queue.is_empty() {
634            let current = backward_queue.pop_front().unwrap();
635
636            // Check if we've met the forward search
637            if forward_visited.contains(&current) {
638                return Ok(Some(reconstruct_bidirectional_path_digraph(
639                    graph,
640                    start_idx,
641                    goal_idx,
642                    current,
643                    &forward_parent,
644                    &backward_parent,
645                )));
646            }
647
648            for neighbor in graph
649                .inner()
650                .neighbors_directed(current, petgraph::Direction::Incoming)
651            {
652                if !backward_visited.contains(&neighbor) {
653                    backward_visited.insert(neighbor);
654                    backward_parent.insert(neighbor, current);
655                    backward_queue.push_back(neighbor);
656                }
657            }
658        }
659    }
660
661    Ok(None)
662}
663
664/// Helper function to reconstruct path from bidirectional search
665#[allow(dead_code)]
666fn reconstruct_bidirectional_path<N, E, Ix>(
667    graph: &Graph<N, E, Ix>,
668    start_idx: petgraph::graph::NodeIndex<Ix>,
669    goal_idx: petgraph::graph::NodeIndex<Ix>,
670    meeting_point: petgraph::graph::NodeIndex<Ix>,
671    forward_parent: &std::collections::HashMap<
672        petgraph::graph::NodeIndex<Ix>,
673        petgraph::graph::NodeIndex<Ix>,
674    >,
675    backward_parent: &std::collections::HashMap<
676        petgraph::graph::NodeIndex<Ix>,
677        petgraph::graph::NodeIndex<Ix>,
678    >,
679) -> Vec<N>
680where
681    N: Node + std::fmt::Debug + Clone,
682    E: EdgeWeight,
683    Ix: petgraph::graph::IndexType,
684{
685    let mut forward_path = Vec::new();
686    let mut current = meeting_point;
687
688    // Build forward path (from start to meeting point)
689    while current != start_idx {
690        forward_path.push(graph.inner()[current].clone());
691        current = forward_parent[&current];
692    }
693    forward_path.push(graph.inner()[start_idx].clone());
694    forward_path.reverse();
695
696    // Build backward path (from meeting _point to goal)
697    let mut backward_path = Vec::new();
698    current = meeting_point;
699    while current != goal_idx {
700        if let Some(&parent) = backward_parent.get(&current) {
701            current = parent;
702            backward_path.push(graph.inner()[current].clone());
703        } else {
704            break;
705        }
706    }
707
708    // Combine paths
709    let mut full_path = forward_path;
710    full_path.extend(backward_path);
711    full_path
712}
713
714/// Helper function to reconstruct path from bidirectional search for directed graphs
715#[allow(dead_code)]
716fn reconstruct_bidirectional_path_digraph<N, E, Ix>(
717    graph: &DiGraph<N, E, Ix>,
718    start_idx: petgraph::graph::NodeIndex<Ix>,
719    goal_idx: petgraph::graph::NodeIndex<Ix>,
720    meeting_point: petgraph::graph::NodeIndex<Ix>,
721    forward_parent: &std::collections::HashMap<
722        petgraph::graph::NodeIndex<Ix>,
723        petgraph::graph::NodeIndex<Ix>,
724    >,
725    backward_parent: &std::collections::HashMap<
726        petgraph::graph::NodeIndex<Ix>,
727        petgraph::graph::NodeIndex<Ix>,
728    >,
729) -> Vec<N>
730where
731    N: Node + std::fmt::Debug + Clone,
732    E: EdgeWeight,
733    Ix: petgraph::graph::IndexType,
734{
735    let mut forward_path = Vec::new();
736    let mut current = meeting_point;
737
738    // Build forward path (from start to meeting point)
739    while current != start_idx {
740        forward_path.push(graph.inner()[current].clone());
741        current = forward_parent[&current];
742    }
743    forward_path.push(graph.inner()[start_idx].clone());
744    forward_path.reverse();
745
746    // Build backward path (from meeting _point to goal)
747    let mut backward_path = Vec::new();
748    current = meeting_point;
749    while current != goal_idx {
750        if let Some(&parent) = backward_parent.get(&current) {
751            current = parent;
752            backward_path.push(graph.inner()[current].clone());
753        } else {
754            break;
755        }
756    }
757
758    // Combine paths
759    let mut full_path = forward_path;
760    full_path.extend(backward_path);
761    full_path
762}
763
764#[cfg(test)]
765mod tests {
766    use super::*;
767
768    #[test]
769    fn test_breadth_first_search() {
770        let mut graph: Graph<i32, f64> = Graph::new();
771
772        // Create a simple graph: 1 -- 2 -- 3
773        //                             |
774        //                             4
775        graph.add_edge(1, 2, 1.0).unwrap();
776        graph.add_edge(2, 3, 1.0).unwrap();
777        graph.add_edge(2, 4, 1.0).unwrap();
778
779        let result = breadth_first_search(&graph, &1).unwrap();
780
781        // BFS should visit nodes level by level
782        assert_eq!(result[0], 1);
783        assert_eq!(result[1], 2);
784        // 3 and 4 can be in any order as they're at the same level
785        assert!(result.contains(&3));
786        assert!(result.contains(&4));
787        assert_eq!(result.len(), 4);
788    }
789
790    #[test]
791    fn test_breadth_first_search_digraph() {
792        let mut graph: DiGraph<i32, f64> = DiGraph::new();
793
794        // Create a directed graph: 1 -> 2 -> 3
795        //                               |
796        //                               v
797        //                               4
798        graph.add_edge(1, 2, 1.0).unwrap();
799        graph.add_edge(2, 3, 1.0).unwrap();
800        graph.add_edge(2, 4, 1.0).unwrap();
801
802        let result = breadth_first_search_digraph(&graph, &1).unwrap();
803
804        assert_eq!(result[0], 1);
805        assert_eq!(result[1], 2);
806        assert!(result.contains(&3));
807        assert!(result.contains(&4));
808        assert_eq!(result.len(), 4);
809    }
810
811    #[test]
812    fn test_depth_first_search() {
813        let mut graph: Graph<i32, f64> = Graph::new();
814
815        // Create a simple graph
816        graph.add_edge(1, 2, 1.0).unwrap();
817        graph.add_edge(1, 3, 1.0).unwrap();
818        graph.add_edge(2, 4, 1.0).unwrap();
819
820        let result = depth_first_search(&graph, &1).unwrap();
821
822        // DFS should visit nodes depth-first
823        assert_eq!(result[0], 1);
824        assert_eq!(result.len(), 4);
825        assert!(result.contains(&2));
826        assert!(result.contains(&3));
827        assert!(result.contains(&4));
828    }
829
830    #[test]
831    fn test_depth_first_search_digraph() {
832        let mut graph: DiGraph<i32, f64> = DiGraph::new();
833
834        // Create a directed graph
835        graph.add_edge(1, 2, 1.0).unwrap();
836        graph.add_edge(1, 3, 1.0).unwrap();
837        graph.add_edge(2, 4, 1.0).unwrap();
838
839        let result = depth_first_search_digraph(&graph, &1).unwrap();
840
841        assert_eq!(result[0], 1);
842        assert_eq!(result.len(), 4);
843        assert!(result.contains(&2));
844        assert!(result.contains(&3));
845        assert!(result.contains(&4));
846    }
847
848    #[test]
849    fn test_bidirectional_search() {
850        let mut graph: Graph<i32, f64> = Graph::new();
851
852        // Create a chain: 1 -- 2 -- 3 -- 4 -- 5
853        graph.add_edge(1, 2, 1.0).unwrap();
854        graph.add_edge(2, 3, 1.0).unwrap();
855        graph.add_edge(3, 4, 1.0).unwrap();
856        graph.add_edge(4, 5, 1.0).unwrap();
857
858        // Test finding path from 1 to 5
859        let path = bidirectional_search(&graph, &1, &5).unwrap().unwrap();
860        assert_eq!(path[0], 1);
861        assert_eq!(path[path.len() - 1], 5);
862        assert!(path.len() <= 5);
863
864        // Test path from node to itself
865        let same_path = bidirectional_search(&graph, &3, &3).unwrap().unwrap();
866        assert_eq!(same_path, vec![3]);
867
868        // Test disconnected graph
869        let mut disconnected: Graph<i32, f64> = Graph::new();
870        disconnected.add_node(1);
871        disconnected.add_node(2);
872        let no_path = bidirectional_search(&disconnected, &1, &2).unwrap();
873        assert_eq!(no_path, None);
874    }
875
876    #[test]
877    fn test_bidirectional_search_digraph() {
878        let mut graph: DiGraph<i32, f64> = DiGraph::new();
879
880        // Create a directed chain: 1 -> 2 -> 3 -> 4 -> 5
881        graph.add_edge(1, 2, 1.0).unwrap();
882        graph.add_edge(2, 3, 1.0).unwrap();
883        graph.add_edge(3, 4, 1.0).unwrap();
884        graph.add_edge(4, 5, 1.0).unwrap();
885
886        // Test finding path from 1 to 5
887        let path = bidirectional_search_digraph(&graph, &1, &5)
888            .unwrap()
889            .unwrap();
890        assert_eq!(path[0], 1);
891        assert_eq!(path[path.len() - 1], 5);
892
893        // Test no path in wrong direction
894        let no_path = bidirectional_search_digraph(&graph, &5, &1).unwrap();
895        assert_eq!(no_path, None);
896    }
897
898    #[test]
899    fn test_priority_first_search() {
900        let mut graph: Graph<i32, f64> = Graph::new();
901
902        // Create a simple graph: 1 -- 2 -- 3
903        //                             |
904        //                             4
905        graph.add_edge(1, 2, 1.0).unwrap();
906        graph.add_edge(2, 3, 1.0).unwrap();
907        graph.add_edge(2, 4, 1.0).unwrap();
908
909        // Test with node value as priority (lower values first)
910        let result = priority_first_search(&graph, &1, |node| *node).unwrap();
911
912        // Should visit in order: 1, 2, 3, 4 (based on node values)
913        assert_eq!(result[0], 1);
914        assert_eq!(result[1], 2);
915        assert!(result.contains(&3));
916        assert!(result.contains(&4));
917        assert_eq!(result.len(), 4);
918
919        // Test with reverse priority (higher values first)
920        let result_reverse = priority_first_search(&graph, &1, |node| -node).unwrap();
921        assert_eq!(result_reverse[0], 1);
922        // Since 2 is connected to 1, it should be visited next
923        assert_eq!(result_reverse[1], 2);
924        // 4 should come before 3 when using reverse priority
925        assert!(
926            result_reverse.iter().position(|&x| x == 4)
927                < result_reverse.iter().position(|&x| x == 3)
928        );
929    }
930
931    #[test]
932    fn test_priority_first_search_digraph() {
933        let mut graph: DiGraph<i32, f64> = DiGraph::new();
934
935        // Create a directed graph: 1 -> 2 -> 3
936        //                               |
937        //                               v
938        //                               4
939        graph.add_edge(1, 2, 1.0).unwrap();
940        graph.add_edge(2, 3, 1.0).unwrap();
941        graph.add_edge(2, 4, 1.0).unwrap();
942
943        // Test with node value as priority
944        let result = priority_first_search_digraph(&graph, &1, |node| *node).unwrap();
945
946        assert_eq!(result[0], 1);
947        assert_eq!(result[1], 2);
948        assert!(result.contains(&3));
949        assert!(result.contains(&4));
950        assert_eq!(result.len(), 4);
951
952        // Test with constant priority (should behave like BFS)
953        let result_constant = priority_first_search_digraph(&graph, &1, |_| 1).unwrap();
954        assert_eq!(result_constant[0], 1);
955        assert_eq!(result_constant[1], 2);
956        assert_eq!(result_constant.len(), 4);
957    }
958}