1use crate::error::GraphError;
10use crate::graph::Graph;
11use crate::unionfind::UnionFind;
12use core::fmt::Display;
13use core::ops::Add;
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct SpanningTree<W> {
18 pub edges: Vec<usize>,
20 pub total_weight: W,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct MstCertificate {
27 pub edge_ids: Vec<usize>,
29 pub total_weight_repr: String,
31}
32
33impl<W: Display> SpanningTree<W> {
34 pub fn certificate(&self) -> MstCertificate {
36 MstCertificate {
37 edge_ids: self.edges.clone(),
38 total_weight_repr: format!("{}", self.total_weight),
39 }
40 }
41}
42
43fn invalid(msg: &str) -> GraphError {
44 GraphError::CertificateInvalid(msg.to_string())
45}
46
47fn tree_path_max<W: Ord + Clone>(
50 n: usize,
51 tree_adj: &[Vec<(usize, W)>],
52 u: usize,
53 v: usize,
54) -> Option<W> {
55 let mut max_w: Vec<Option<W>> = vec![None; n];
56 let mut visited = vec![false; n];
57 let mut queue = std::collections::VecDeque::new();
58 visited[u] = true;
59 queue.push_back(u);
60 while let Some(x) = queue.pop_front() {
61 if x == v {
62 return max_w[x].clone();
63 }
64 for (y, w) in &tree_adj[x] {
65 if !visited[*y] {
66 visited[*y] = true;
67 let cand = match &max_w[x] {
68 Some(m) if m >= w => m.clone(),
69 _ => w.clone(),
70 };
71 max_w[*y] = Some(cand);
72 queue.push_back(*y);
73 }
74 }
75 }
76 None
77}
78
79pub fn verify_mst<N, W>(graph: &Graph<N, W>, cert: &MstCertificate) -> Result<(), GraphError>
81where
82 W: Ord + Clone + Default + Add<Output = W> + Display,
83{
84 graph.validate()?;
85 let n = graph.node_count();
86
87 let mut tree_edges = Vec::with_capacity(cert.edge_ids.len());
89 for &id in &cert.edge_ids {
90 let e = graph
91 .edges
92 .get(id)
93 .ok_or_else(|| invalid("unknown edge id"))?;
94 if e.is_self_loop() {
95 return Err(invalid("self-loop in tree"));
96 }
97 tree_edges.push(e);
98 }
99
100 let expected = n.saturating_sub(1);
102 if tree_edges.len() != expected {
103 return Err(invalid("wrong edge count for a spanning tree"));
104 }
105
106 let mut uf = UnionFind::new(n);
108 for e in &tree_edges {
109 if !uf.union(e.source, e.target) {
110 return Err(invalid("tree contains a cycle"));
111 }
112 }
113 if n > 0 {
114 let root = uf.find(0);
115 for i in 1..n {
116 if uf.find(i) != root {
117 return Err(invalid("tree does not span the graph"));
118 }
119 }
120 }
121
122 let mut total = W::default();
124 for e in &tree_edges {
125 total = total + e.weight.clone();
126 }
127 if format!("{total}") != cert.total_weight_repr {
128 return Err(invalid("total weight mismatch"));
129 }
130
131 let tree_ids: std::collections::HashSet<usize> = cert.edge_ids.iter().copied().collect();
134 let mut tree_adj: Vec<Vec<(usize, W)>> = vec![Vec::new(); n];
135 for e in &tree_edges {
136 tree_adj[e.source].push((e.target, e.weight.clone()));
137 tree_adj[e.target].push((e.source, e.weight.clone()));
138 }
139 for e in &graph.edges {
140 if tree_ids.contains(&e.id) || e.is_self_loop() {
141 continue;
142 }
143 if let Some(path_max) = tree_path_max(n, &tree_adj, e.source, e.target)
144 && e.weight < path_max
145 {
146 return Err(invalid("not minimal: a cheaper spanning tree exists"));
147 }
148 }
149 Ok(())
150}
151
152#[derive(Debug, Clone, PartialEq, Eq)]
154pub struct ShortestPathCertificate {
155 pub source: usize,
157 pub predecessors: Vec<Option<usize>>,
160}
161
162fn edge_weight<N>(graph: &Graph<N, i64>, from: usize, to: usize) -> Option<i64> {
164 let undirected = !graph.is_directed();
165 graph
166 .edges
167 .iter()
168 .filter(|e| {
169 (e.source == from && e.target == to)
170 || (undirected && e.source == to && e.target == from)
171 })
172 .map(|e| e.weight)
173 .min()
174}
175
176fn tree_dist<N>(
179 graph: &Graph<N, i64>,
180 cert: &ShortestPathCertificate,
181 v: usize,
182 memo: &mut [Option<Option<i64>>],
183 visiting: &mut [bool],
184) -> Result<Option<i64>, GraphError> {
185 if v == cert.source {
186 return Ok(Some(0));
187 }
188 if let Some(d) = memo[v] {
189 return Ok(d);
190 }
191 let result = match cert.predecessors[v] {
192 None => None,
193 Some(u) => {
194 if visiting[v] {
195 return Err(invalid("predecessor cycle"));
196 }
197 visiting[v] = true;
198 let du = tree_dist(graph, cert, u, memo, visiting)?;
199 visiting[v] = false;
200 match du {
201 None => return Err(invalid("predecessor points outside the tree")),
202 Some(d_u) => {
203 let w = edge_weight(graph, u, v)
204 .ok_or_else(|| invalid("predecessor edge missing"))?;
205 Some(d_u + w)
206 }
207 }
208 }
209 };
210 memo[v] = Some(result);
211 Ok(result)
212}
213
214pub fn verify_shortest_paths<N>(
216 graph: &Graph<N, i64>,
217 cert: &ShortestPathCertificate,
218) -> Result<(), GraphError> {
219 graph.validate()?;
220 let n = graph.node_count();
221 if cert.source >= n {
222 return Err(invalid("source out of range"));
223 }
224 if cert.predecessors.len() != n {
225 return Err(invalid("predecessor length mismatch"));
226 }
227 if cert.predecessors[cert.source].is_some() {
228 return Err(invalid("source must have no predecessor"));
229 }
230
231 let mut memo: Vec<Option<Option<i64>>> = vec![None; n];
232 let mut visiting = vec![false; n];
233 let mut dist = vec![None; n];
234 for (v, slot) in dist.iter_mut().enumerate() {
235 *slot = tree_dist(graph, cert, v, &mut memo, &mut visiting)?;
236 }
237
238 let undirected = !graph.is_directed();
241 for e in &graph.edges {
242 let mut arcs = vec![(e.source, e.target)];
243 if undirected {
244 arcs.push((e.target, e.source));
245 }
246 for (a, b) in arcs {
247 if let Some(da) = dist[a] {
248 match dist[b] {
249 None => return Err(invalid("reachable node missing from tree")),
250 Some(db) => {
251 if db > da + e.weight {
252 return Err(invalid("edge violates shortest-path relaxation"));
253 }
254 }
255 }
256 }
257 }
258 }
259 Ok(())
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265 use crate::edge::Directedness;
266 use crate::path::bellman_ford;
267
268 fn weighted() -> Graph<u8, i64> {
269 let mut g = Graph::with_nodes(vec![0, 1, 2, 3], Directedness::Directed);
270 g.add_edge(0, 1, 1).unwrap();
271 g.add_edge(1, 2, 2).unwrap();
272 g.add_edge(0, 2, 5).unwrap();
273 g.add_edge(2, 3, 1).unwrap();
274 g
275 }
276
277 #[test]
278 fn valid_shortest_path_cert_verifies() {
279 let g = weighted();
280 let (res, _) = bellman_ford(&g, 0).unwrap();
281 let cert = ShortestPathCertificate {
282 source: 0,
283 predecessors: res.predecessors,
284 };
285 assert!(verify_shortest_paths(&g, &cert).is_ok());
286 }
287
288 #[test]
289 fn tampered_shortest_path_cert_rejected() {
290 let g = weighted();
291 let (res, _) = bellman_ford(&g, 0).unwrap();
292 let mut preds = res.predecessors;
293 preds[2] = Some(0);
296 let cert = ShortestPathCertificate {
297 source: 0,
298 predecessors: preds,
299 };
300 assert!(verify_shortest_paths(&g, &cert).is_err());
301 }
302}