Skip to content

Commit 278c53b

Browse files
Add Prim's Algorithm for Minimum Spanning Tree (petgraph#625)
Prim's algorithm: https://en.wikipedia.org/wiki/Prim%27s_algorithm This PR adds another algorithm to find the Minimum Spanning Tree of a graph, an alternative to Kruskal's. 1. Prim's algorithm has some limitations, such as the input graph must be undirected and it should not have disconnected components. I'm wondering if this can be enforced with traits, if anyone can help on this. 2. I would like to suggest adding another algorithm as well, `min_spanning_forest_prim`, that iterates through this algorithm to find min spanning tree for all input graph components. `min_spanning_forest_kruskal` would be trivial to implement, since current `min_spanning_tree` function, using Kruskal's algorithm, already finds a min spanning forest. Any thoughts on this? This PR also aims to fix current `min_spanning_tree` benches, since they currently do not iterate through the created `MinSpanningTree` structure, not giving the actual bench for Kruskal algorithm. 3. I added a simple function to iterate through the generated tree elements, but I'm not very familiar with Rust ecosystem, so I would appreciate if anyone knows a better way to force the iteration: ```Rust // Current Bench: bench.iter(|| (min_spanning_tree(&a), min_spanning_tree(&b))); // Suggested Bench: bench.iter(|| (iterate_mst_kruskal(&a), iterate_mst_kruskal(&b))); // Force Tree Iteration: fn iterate_mst_kruskal<G>(g: G) -> bool where G: Data + IntoEdges + IntoNodeReferences + IntoEdgeReferences + NodeIndexable, G::NodeWeight: Clone, G::EdgeWeight: Clone + PartialOrd, { let mst = min_spanning_tree(g); mst.into_iter().all(|_| true) } ``` --------- Co-authored-by: Sambinelli <[email protected]>
1 parent 30939fe commit 278c53b

File tree

4 files changed

+375
-18
lines changed

4 files changed

+375
-18
lines changed

benches/min_spanning_tree.rs

+62-13
Original file line numberDiff line numberDiff line change
@@ -9,52 +9,101 @@ use test::Bencher;
99
mod common;
1010
use common::{digraph, ungraph};
1111

12-
use petgraph::algo::min_spanning_tree;
12+
use petgraph::{
13+
algo::{min_spanning_tree, min_spanning_tree_prim},
14+
visit::{Data, IntoEdgeReferences, IntoEdges, IntoNodeReferences, NodeIndexable},
15+
Graph, Undirected,
16+
};
1317

1418
#[bench]
15-
fn min_spanning_tree_praust_undir_bench(bench: &mut Bencher) {
19+
fn min_spanning_tree_kruskal_praust_undir_bench(bench: &mut Bencher) {
1620
let a = ungraph().praust_a();
1721
let b = ungraph().praust_b();
1822

19-
bench.iter(|| (min_spanning_tree(&a), min_spanning_tree(&b)));
23+
bench.iter(|| (iterate_mst_kruskal(&a), iterate_mst_kruskal(&b)));
2024
}
2125

2226
#[bench]
23-
fn min_spanning_tree_praust_dir_bench(bench: &mut Bencher) {
27+
fn min_spanning_tree_kruskal_praust_dir_bench(bench: &mut Bencher) {
2428
let a = digraph().praust_a();
2529
let b = digraph().praust_b();
2630

27-
bench.iter(|| (min_spanning_tree(&a), min_spanning_tree(&b)));
31+
bench.iter(|| (iterate_mst_kruskal(&a), iterate_mst_kruskal(&b)));
2832
}
2933

3034
#[bench]
31-
fn min_spanning_tree_full_undir_bench(bench: &mut Bencher) {
35+
fn min_spanning_tree_kruskal_full_undir_bench(bench: &mut Bencher) {
3236
let a = ungraph().full_a();
3337
let b = ungraph().full_b();
3438

35-
bench.iter(|| (min_spanning_tree(&a), min_spanning_tree(&b)));
39+
bench.iter(|| (iterate_mst_kruskal(&a), iterate_mst_kruskal(&b)));
3640
}
3741

3842
#[bench]
39-
fn min_spanning_tree_full_dir_bench(bench: &mut Bencher) {
43+
fn min_spanning_tree_kruskal_full_dir_bench(bench: &mut Bencher) {
4044
let a = digraph().full_a();
4145
let b = digraph().full_b();
4246

43-
bench.iter(|| (min_spanning_tree(&a), min_spanning_tree(&b)));
47+
bench.iter(|| (iterate_mst_kruskal(&a), iterate_mst_kruskal(&b)));
4448
}
4549

4650
#[bench]
47-
fn min_spanning_tree_petersen_undir_bench(bench: &mut Bencher) {
51+
fn min_spanning_tree_kruskal_petersen_undir_bench(bench: &mut Bencher) {
4852
let a = ungraph().petersen_a();
4953
let b = ungraph().petersen_b();
5054

51-
bench.iter(|| (min_spanning_tree(&a), min_spanning_tree(&b)));
55+
bench.iter(|| (iterate_mst_kruskal(&a), iterate_mst_kruskal(&b)));
5256
}
5357

5458
#[bench]
55-
fn min_spanning_tree_petersen_dir_bench(bench: &mut Bencher) {
59+
fn min_spanning_tree_kruskal_petersen_dir_bench(bench: &mut Bencher) {
5660
let a = digraph().petersen_a();
5761
let b = digraph().petersen_b();
5862

59-
bench.iter(|| (min_spanning_tree(&a), min_spanning_tree(&b)));
63+
bench.iter(|| (iterate_mst_kruskal(&a), iterate_mst_kruskal(&b)));
64+
}
65+
66+
#[bench]
67+
fn min_spanning_tree_prim_praust_undir_bench(bench: &mut Bencher) {
68+
let a = ungraph().praust_a();
69+
let b = ungraph().praust_b();
70+
71+
bench.iter(|| (iterate_mst_prim(&a), iterate_mst_prim(&b)));
72+
}
73+
74+
#[bench]
75+
fn min_spanning_tree_prim_full_undir_bench(bench: &mut Bencher) {
76+
let a = ungraph().full_a();
77+
let b = ungraph().full_b();
78+
79+
bench.iter(|| (iterate_mst_prim(&a), iterate_mst_prim(&b)));
80+
}
81+
82+
#[bench]
83+
fn min_spanning_tree_prim_petersen_undir_bench(bench: &mut Bencher) {
84+
let a: Graph<(), (), Undirected> = ungraph().petersen_a();
85+
let b = ungraph().petersen_b();
86+
87+
bench.iter(|| (iterate_mst_prim(&a), iterate_mst_prim(&b)));
88+
}
89+
90+
fn iterate_mst_kruskal<G>(g: G)
91+
where
92+
G: Data + IntoEdges + IntoNodeReferences + IntoEdgeReferences + NodeIndexable,
93+
G::NodeWeight: Clone,
94+
G::EdgeWeight: Clone + PartialOrd,
95+
{
96+
for e in min_spanning_tree(g) {
97+
std::hint::black_box(e);
98+
}
99+
}
100+
fn iterate_mst_prim<G>(g: G)
101+
where
102+
G: Data + IntoEdges + IntoNodeReferences + IntoEdgeReferences + NodeIndexable,
103+
G::NodeWeight: Clone,
104+
G::EdgeWeight: Clone + PartialOrd,
105+
{
106+
for e in min_spanning_tree_prim(g) {
107+
std::hint::black_box(e);
108+
}
60109
}

src/algo/min_spanning_tree.rs

+143-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
//! Minimum Spanning Tree algorithms.
22
3-
use std::collections::{BinaryHeap, HashMap};
3+
use std::collections::{BinaryHeap, HashMap, HashSet};
44

55
use crate::prelude::*;
66

77
use crate::data::Element;
88
use crate::scored::MinScored;
99
use crate::unionfind::UnionFind;
10-
use crate::visit::{Data, IntoNodeReferences, NodeRef};
10+
use crate::visit::{Data, IntoEdges, IntoNodeReferences, NodeRef};
1111
use crate::visit::{IntoEdgeReferences, NodeIndexable};
1212

1313
/// \[Generic\] Compute a *minimum spanning tree* of a graph.
@@ -22,6 +22,10 @@ use crate::visit::{IntoEdgeReferences, NodeIndexable};
2222
/// and **|V| - c** edges, where **c** is the number of connected components in `g`.
2323
///
2424
/// Use `from_elements` to create a graph from the resulting iterator.
25+
///
26+
/// See also: [`.min_spanning_tree_prim(g)`][1] for implementation using Prim's algorithm.
27+
///
28+
/// [1]: fn.min_spanning_tree_prim.html
2529
pub fn min_spanning_tree<G>(g: G) -> MinSpanningTree<G>
2630
where
2731
G::NodeWeight: Clone,
@@ -52,6 +56,8 @@ where
5256
}
5357

5458
/// An iterator producing a minimum spanning forest of a graph.
59+
/// It will first iterate all Node elements from original graph,
60+
/// then iterate Edge elements from computed minimum spanning forest.
5561
#[derive(Debug, Clone)]
5662
pub struct MinSpanningTree<G>
5763
where
@@ -115,3 +121,138 @@ where
115121
None
116122
}
117123
}
124+
125+
/// \[Generic\] Compute a *minimum spanning tree* of a graph using Prim's algorithm.
126+
///
127+
/// Graph is treated as if undirected. The computed minimum spanning tree can be wrong
128+
/// if this is not true.
129+
///
130+
/// Graph is treated as if connected (has only 1 component). Otherwise, the resulting
131+
/// graph will only contain edges for an arbitrary minimum spanning tree for a single component.
132+
///
133+
/// Using Prim's algorithm with runtime **O(|E| log |V|)**.
134+
///
135+
/// The resulting graph has all the vertices of the input graph (with identical node indices),
136+
/// and **|V| - 1** edges if input graph is connected, and |W| edges if disconnected, where |W| < |V| - 1.
137+
///
138+
/// Use `from_elements` to create a graph from the resulting iterator.
139+
///
140+
/// See also: [`.min_spanning_tree(g)`][1] for implementation using Kruskal's algorithm and support for minimum spanning forest.
141+
///
142+
/// [1]: fn.min_spanning_tree.html
143+
pub fn min_spanning_tree_prim<G>(g: G) -> MinSpanningTreePrim<G>
144+
where
145+
G::EdgeWeight: PartialOrd,
146+
G: IntoNodeReferences + IntoEdgeReferences,
147+
{
148+
let sort_edges = BinaryHeap::with_capacity(g.edge_references().size_hint().0);
149+
let nodes_taken = HashSet::with_capacity(g.node_references().size_hint().0);
150+
let initial_node = g.node_references().next();
151+
152+
MinSpanningTreePrim {
153+
graph: g,
154+
node_ids: Some(g.node_references()),
155+
node_map: HashMap::new(),
156+
node_count: 0,
157+
sort_edges,
158+
nodes_taken,
159+
initial_node,
160+
}
161+
}
162+
163+
/// An iterator producing a minimum spanning tree of a graph.
164+
/// It will first iterate all Node elements from original graph,
165+
/// then iterate Edge elements from computed minimum spanning tree.
166+
#[derive(Debug, Clone)]
167+
pub struct MinSpanningTreePrim<G>
168+
where
169+
G: IntoNodeReferences,
170+
{
171+
graph: G,
172+
node_ids: Option<G::NodeReferences>,
173+
node_map: HashMap<usize, usize>,
174+
node_count: usize,
175+
#[allow(clippy::type_complexity)]
176+
sort_edges: BinaryHeap<MinScored<G::EdgeWeight, (G::NodeId, G::NodeId)>>,
177+
nodes_taken: HashSet<usize>,
178+
initial_node: Option<G::NodeRef>,
179+
}
180+
181+
impl<G> Iterator for MinSpanningTreePrim<G>
182+
where
183+
G: IntoNodeReferences + IntoEdges + NodeIndexable,
184+
G::NodeWeight: Clone,
185+
G::EdgeWeight: Clone + PartialOrd,
186+
{
187+
type Item = Element<G::NodeWeight, G::EdgeWeight>;
188+
189+
fn next(&mut self) -> Option<Self::Item> {
190+
// Iterate through Node elements
191+
let g = self.graph;
192+
if let Some(ref mut iter) = self.node_ids {
193+
if let Some(node) = iter.next() {
194+
self.node_map.insert(g.to_index(node.id()), self.node_count);
195+
self.node_count += 1;
196+
return Some(Element::Node {
197+
weight: node.weight().clone(),
198+
});
199+
}
200+
}
201+
self.node_ids = None;
202+
203+
// Bootstrap Prim's algorithm to find MST Edge elements.
204+
// Mark initial node as taken and add its edges to priority queue.
205+
if let Some(initial_node) = self.initial_node {
206+
let initial_node_index = g.to_index(initial_node.id());
207+
self.nodes_taken.insert(initial_node_index);
208+
209+
let initial_edges = g.edges(initial_node.id());
210+
for edge in initial_edges {
211+
self.sort_edges.push(MinScored(
212+
edge.weight().clone(),
213+
(edge.source(), edge.target()),
214+
));
215+
}
216+
};
217+
self.initial_node = None;
218+
219+
// Clear edges queue if all nodes were already included in MST.
220+
if self.nodes_taken.len() == self.node_count {
221+
self.sort_edges.clear();
222+
};
223+
224+
// Prim's algorithm:
225+
// Iterate through Edge elements, adding an edge to the MST iff some of it's nodes are not part of MST yet.
226+
while let Some(MinScored(score, (source, target))) = self.sort_edges.pop() {
227+
let (source_index, target_index) = (g.to_index(source), g.to_index(target));
228+
229+
if self.nodes_taken.contains(&target_index) {
230+
continue;
231+
}
232+
233+
self.nodes_taken.insert(target_index);
234+
for edge in g.edges(target) {
235+
self.sort_edges.push(MinScored(
236+
edge.weight().clone(),
237+
(edge.source(), edge.target()),
238+
));
239+
}
240+
241+
let (&source_order, &target_order) = match (
242+
self.node_map.get(&source_index),
243+
self.node_map.get(&target_index),
244+
) {
245+
(Some(source_order), Some(target_order)) => (source_order, target_order),
246+
_ => panic!("Edge references unknown node"),
247+
};
248+
249+
return Some(Element::Edge {
250+
source: source_order,
251+
target: target_order,
252+
weight: score,
253+
});
254+
}
255+
256+
None
257+
}
258+
}

src/algo/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ pub use isomorphism::{
4747
};
4848
pub use k_shortest_path::k_shortest_path;
4949
pub use matching::{greedy_matching, maximum_matching, Matching};
50-
pub use min_spanning_tree::min_spanning_tree;
50+
pub use min_spanning_tree::{min_spanning_tree, min_spanning_tree_prim};
5151
pub use page_rank::page_rank;
5252
pub use simple_paths::all_simple_paths;
5353

0 commit comments

Comments
 (0)