Skip to main content

god_graph/algorithms/
properties.rs

1//! 图属性检测模块
2//!
3//! 包含连通性、二分性、环检测等图属性检查算法
4
5use crate::graph::traits::{GraphBase, GraphQuery};
6use crate::graph::Graph;
7use crate::node::NodeIndex;
8use std::collections::{HashMap, VecDeque};
9
10/// 检查图是否连通
11pub fn is_connected<T>(graph: &Graph<T, impl Clone>) -> bool {
12    if graph.node_count() == 0 {
13        return true;
14    }
15
16    let first_node = graph.nodes().next().map(|n| n.index()).unwrap();
17    let mut visited = vec![false; graph.node_count()];
18    let mut count = 0;
19
20    let mut queue = VecDeque::new();
21    queue.push_back(first_node);
22    visited[first_node.index()] = true;
23
24    while let Some(node) = queue.pop_front() {
25        count += 1;
26
27        for neighbor in graph.neighbors(node) {
28            if !visited[neighbor.index()] {
29                visited[neighbor.index()] = true;
30                queue.push_back(neighbor);
31            }
32        }
33    }
34
35    count == graph.node_count()
36}
37
38/// 检查图是否为二分图
39pub fn is_bipartite<T>(graph: &Graph<T, impl Clone>) -> bool {
40    let mut color: HashMap<usize, bool> = HashMap::new();
41
42    for start in graph.nodes() {
43        let start_idx = start.index();
44        if color.contains_key(&start_idx.index()) {
45            continue;
46        }
47
48        let mut queue = VecDeque::new();
49        queue.push_back(start_idx.index());
50        color.insert(start_idx.index(), true);
51
52        while let Some(node_idx) = queue.pop_front() {
53            let current_color = color[&node_idx];
54            let node_ni = NodeIndex::new(node_idx, 0);
55
56            for neighbor in graph.neighbors(node_ni) {
57                let neighbor_idx = neighbor.index();
58                match color.get(&neighbor_idx) {
59                    Some(&c) if c == current_color => return false,
60                    None => {
61                        color.insert(neighbor_idx, !current_color);
62                        queue.push_back(neighbor_idx);
63                    }
64                    _ => {}
65                }
66            }
67        }
68    }
69
70    true
71}
72
73/// 检查图是否为 DAG(有向无环图)
74pub fn is_dag<T>(graph: &Graph<T, impl Clone>) -> bool {
75    // 使用拓扑排序检测环
76    crate::algorithms::traversal::topological_sort(graph).is_ok()
77}
78
79/// 检测图中是否存在环
80pub fn has_cycle<T>(graph: &Graph<T, impl Clone>) -> bool {
81    !is_dag(graph)
82}
83
84/// 检查图是否为树
85pub fn is_tree<T>(graph: &Graph<T, impl Clone>) -> bool {
86    let n = graph.node_count();
87    if n == 0 {
88        return true;
89    }
90
91    // 树的定义:连通且边数 = 节点数 - 1
92    is_connected(graph) && graph.edge_count() == n - 1
93}
94
95/// 计算图的直径(最长最短路径)
96pub fn diameter<T>(graph: &Graph<T, impl Clone>) -> usize {
97    if graph.node_count() == 0 {
98        return 0;
99    }
100
101    let mut max_distance = 0;
102
103    for start in graph.nodes() {
104        let mut distances: HashMap<usize, usize> = HashMap::new();
105        let mut queue = VecDeque::new();
106
107        let start_idx = start.index();
108        distances.insert(start_idx.index(), 0);
109        queue.push_back(start_idx);
110
111        while let Some(node) = queue.pop_front() {
112            let node_idx = node.index();
113            let dist = distances[&node_idx];
114            max_distance = max_distance.max(dist);
115
116            let node_ni = NodeIndex::new(node_idx, 0);
117            for neighbor in graph.neighbors(node_ni) {
118                let neighbor_idx = neighbor.index();
119                if let std::collections::hash_map::Entry::Vacant(e) = distances.entry(neighbor_idx)
120                {
121                    e.insert(dist + 1);
122                    queue.push_back(neighbor);
123                }
124            }
125        }
126    }
127
128    max_distance
129}
130
131/// 计算图的密度
132pub fn density<T>(graph: &Graph<T, impl Clone>) -> f64 {
133    let n = graph.node_count();
134    if n <= 1 {
135        return 0.0;
136    }
137
138    let max_edges = n * (n - 1);
139    graph.edge_count() as f64 / max_edges as f64
140}
141
142/// 计算图的 girth(最短环的长度)
143///
144/// Girth 是图中最短环的长度。如果图中没有环,返回 0。
145///
146/// # 复杂度
147/// - 时间:O(V * (V + E)) - 对每个节点进行 BFS
148/// - 空间:O(V)
149///
150/// # 示例
151/// ```
152/// use god_gragh::prelude::*;
153/// use god_gragh::algorithms::properties::girth;
154///
155/// let graph = GraphBuilder::directed()
156///     .with_nodes(vec!["A", "B", "C"])
157///     .with_edges(vec![(0, 1, 1.0), (1, 2, 1.0), (2, 0, 1.0)])
158///     .build()
159///     .unwrap();
160///
161/// let g = girth(&graph);
162/// assert_eq!(g, 3); // 三角形环
163/// ```
164pub fn girth<T>(graph: &Graph<T, impl Clone>) -> usize {
165    use std::collections::VecDeque;
166
167    let n = graph.node_count();
168    if n == 0 {
169        return 0;
170    }
171
172    let node_indices: Vec<_> = graph.nodes().map(|n| n.index()).collect();
173    let mut min_cycle = usize::MAX;
174
175    // 对每个节点进行 BFS,寻找回到自身的最短路径
176    for start in &node_indices {
177        let mut dist = vec![usize::MAX; n];
178        let mut queue = VecDeque::new();
179
180        dist[start.index()] = 0;
181        queue.push_back(*start);
182
183        while let Some(u) = queue.pop_front() {
184            if dist[u.index()] >= min_cycle {
185                continue; // 剪枝:当前路径已经超过已知最短环
186            }
187
188            for v in graph.neighbors(u) {
189                let new_dist = dist[u.index()] + 1;
190
191                // 如果 v 已经访问过,且不是 u 的直接父节点,则找到环
192                if dist[v.index()] != usize::MAX {
193                    if dist[v.index()] != dist[u.index()] - 1 {
194                        let cycle_len = new_dist + dist[v.index()];
195                        min_cycle = min_cycle.min(cycle_len);
196                    }
197                } else {
198                    dist[v.index()] = new_dist;
199                    queue.push_back(v);
200                }
201            }
202        }
203    }
204
205    if min_cycle == usize::MAX {
206        0
207    } else {
208        min_cycle
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215    use crate::graph::builders::GraphBuilder;
216
217    #[test]
218    fn test_is_connected() {
219        let graph = GraphBuilder::undirected()
220            .with_nodes(vec![1, 2, 3])
221            .with_edges(vec![(0, 1, 1.0), (1, 2, 1.0)])
222            .build()
223            .unwrap();
224        assert!(is_connected(&graph));
225    }
226
227    #[test]
228    fn test_is_bipartite() {
229        let graph = GraphBuilder::undirected()
230            .with_nodes(vec![1, 2, 3, 4])
231            .with_edges(vec![(0, 1, 1.0), (1, 2, 1.0), (2, 3, 1.0)])
232            .build()
233            .unwrap();
234        assert!(is_bipartite(&graph));
235    }
236
237    #[test]
238    fn test_is_dag() {
239        let graph = GraphBuilder::directed()
240            .with_nodes(vec![1, 2, 3])
241            .with_edges(vec![(0, 1, 1.0), (1, 2, 1.0)])
242            .build()
243            .unwrap();
244        assert!(is_dag(&graph));
245    }
246
247    #[test]
248    fn test_density() {
249        let graph = GraphBuilder::directed()
250            .with_nodes(vec![1, 2, 3, 4])
251            .with_edges(vec![(0, 1, 1.0), (1, 2, 1.0)])
252            .build()
253            .unwrap();
254        let d = density(&graph);
255        assert!(d > 0.0 && d < 1.0);
256    }
257
258    #[test]
259    fn test_diameter() {
260        let graph = GraphBuilder::directed()
261            .with_nodes(vec![1, 2, 3, 4, 5])
262            .with_edges(vec![(0, 1, 1.0), (1, 2, 1.0), (2, 3, 1.0), (3, 4, 1.0)])
263            .build()
264            .unwrap();
265
266        // 线性图 A->B->C->D->E 的直径是 4
267        let diam = diameter(&graph);
268        assert_eq!(diam, 4);
269    }
270
271    #[test]
272    fn test_girth_triangle() {
273        let graph = GraphBuilder::directed()
274            .with_nodes(vec!["A", "B", "C"])
275            .with_edges(vec![(0, 1, 1.0), (1, 2, 1.0), (2, 0, 1.0)])
276            .build()
277            .unwrap();
278
279        // 三角形环的长度是 3
280        let g = girth(&graph);
281        assert_eq!(g, 3);
282    }
283
284    #[test]
285    fn test_girth_no_cycle() {
286        let graph = GraphBuilder::directed()
287            .with_nodes(vec!["A", "B", "C"])
288            .with_edges(vec![(0, 1, 1.0), (1, 2, 1.0)])
289            .build()
290            .unwrap();
291
292        // 无环图的 girth 是 0
293        let g = girth(&graph);
294        assert_eq!(g, 0);
295    }
296
297    #[test]
298    fn test_has_cycle() {
299        let graph_with_cycle = GraphBuilder::directed()
300            .with_nodes(vec!["A", "B", "C"])
301            .with_edges(vec![(0, 1, 1.0), (1, 2, 1.0), (2, 0, 1.0)])
302            .build()
303            .unwrap();
304        assert!(has_cycle(&graph_with_cycle));
305
306        let graph_without_cycle = GraphBuilder::directed()
307            .with_nodes(vec!["A", "B", "C"])
308            .with_edges(vec![(0, 1, 1.0), (1, 2, 1.0)])
309            .build()
310            .unwrap();
311        assert!(!has_cycle(&graph_without_cycle));
312    }
313
314    #[test]
315    fn test_is_tree() {
316        let tree = GraphBuilder::undirected()
317            .with_nodes(vec![1, 2, 3, 4])
318            .with_edges(vec![(0, 1, 1.0), (0, 2, 1.0), (0, 3, 1.0)])
319            .build()
320            .unwrap();
321        assert!(is_tree(&tree));
322
323        let not_tree = GraphBuilder::undirected()
324            .with_nodes(vec![1, 2, 3])
325            .with_edges(vec![(0, 1, 1.0), (1, 2, 1.0), (2, 0, 1.0)])
326            .build()
327            .unwrap();
328        assert!(!is_tree(&not_tree));
329    }
330}