@@ -27,28 +27,53 @@ use rayon::prelude::*;
27
27
28
28
use crate :: iterators:: WeightedEdgeList ;
29
29
30
- /// Find the edges in the minimum spanning tree or forest of a graph
30
+ /// Find the edges in the minimum spanning tree or forest of an undirected graph
31
31
/// using Kruskal's algorithm.
32
32
///
33
- /// :param PyGraph graph: Undirected graph
34
- /// :param weight_fn: A callable object (function, lambda, etc) which
35
- /// will be passed the edge object and expected to return a ``float``. This
36
- /// tells rustworkx/rust how to extract a numerical weight as a ``float``
37
- /// for edge object. Some simple examples are::
33
+ /// A Minimum Spanning Tree (MST) is a subset of the edges of a connected,
34
+ /// undirected graph that connects all vertices together without cycles and
35
+ /// with the minimum possible total edge weight.
38
36
///
39
- /// minimum_spanning_edges(graph, weight_fn: lambda x: 1)
37
+ /// This function computes the edges that form the MST or forest of an
38
+ /// undirected graph. Kruskal's algorithm works by sorting all the edges in the
39
+ /// graph by their weights and then adding them one by one to the MST, ensuring
40
+ /// that no cycles are formed.
40
41
///
41
- /// to return a weight of 1 for all edges. Also::
42
+ /// >>> G = rx.PyGraph()
43
+ /// >>> G.add_nodes_from(["A", "B", "C", "D"])
44
+ /// NodeIndices[0, 1, 2, 3]
45
+ /// >>> G.add_edges_from([(0, 1, 10), (0, 2, 6), (0, 3, 5), (1, 3, 15), (2, 3, 4)])
46
+ /// EdgeIndices[0, 1, 2, 3, 4]
47
+ /// >>> rx.minimum_spanning_edges(G, weight_fn=lambda x: x)
48
+ /// WeightedEdgeList[(2, 3, 4), (0, 3, 5), (0, 1, 10)]
42
49
///
43
- /// minimum_spanning_edges(graph, weight_fn: float)
50
+ /// In this example, the edge `(0, 2, 6)` won't become part of the MST because
51
+ /// in the moment it's considered, the two other edges connecting nodes
52
+ /// 0-3-2 are already parts of MST because of their lower weight.
44
53
///
45
- /// to cast the edge object as a float as the weight.
46
- /// :param float default_weight: If ``weight_fn`` isn't specified this optional
54
+ /// To obtain the result as a graph, see :func:`~minimum_spanning_tree`.
55
+ ///
56
+ /// :param PyGraph graph: An undirected graph
57
+ /// :param weight_fn: A callable object (function, lambda, etc) that takes
58
+ /// an edge object and returns a ``float``. This function is used to
59
+ /// extract the numerical weight for each edge. For example:
60
+ ///
61
+ /// rx.minimum_spanning_edges(G, weight_fn=lambda x: 1)
62
+ ///
63
+ /// will assign a weight of 1 to all edges and thus ignore the real weights.
64
+ ///
65
+ /// rx.minimum_spanning_edges(G, weight_fn=float)
66
+ ///
67
+ /// will just cast the edge object to a ``float`` to determine its weight.
68
+ /// :param float default_weight: If ``weight_fn`` isn't specified, this optional
47
69
/// float value will be used for the weight/cost of each edge.
48
70
///
49
- /// :returns: The :math:`N - |c|` edges of the Minimum Spanning Tree (or Forest, if :math:`|c| > 1`)
50
- /// where :math:`N` is the number of nodes and :math:`|c|` is the number of connected components of the graph
71
+ /// :returns: The :math:`N - |c|` edges of the Minimum Spanning Tree (or Forest,
72
+ /// if :math:`|c| > 1`) where :math:`N` is the number of nodes and
73
+ /// :math:`|c|` is the number of connected components of the graph.
51
74
/// :rtype: WeightedEdgeList
75
+ ///
76
+ /// :raises ValueError: If a NaN value is found (or computed) as an edge weight.
52
77
#[ pyfunction]
53
78
#[ pyo3( signature=( graph, weight_fn=None , default_weight=1.0 ) , text_signature = "(graph, weight_fn=None, default_weight=1.0)" ) ]
54
79
pub fn minimum_spanning_edges (
@@ -69,52 +94,72 @@ pub fn minimum_spanning_edges(
69
94
edge_list. push ( ( weight, edge) ) ;
70
95
}
71
96
72
- edge_list. par_sort_unstable_by ( |a, b| {
73
- let weight_a = a. 0 ;
74
- let weight_b = b. 0 ;
75
- weight_a. partial_cmp ( & weight_b) . unwrap_or ( Ordering :: Less )
97
+ edge_list. par_sort_unstable_by ( |( weight_a, _) , ( weight_b, _) | {
98
+ weight_a. partial_cmp ( weight_b) . unwrap_or ( Ordering :: Less )
76
99
} ) ;
77
100
78
- let mut answer: Vec < ( usize , usize , PyObject ) > = Vec :: new ( ) ;
79
- for float_edge_pair in edge_list. iter ( ) {
80
- let edge = float_edge_pair. 1 ;
101
+ let mut mst_edges: Vec < ( usize , usize , PyObject ) > = Vec :: new ( ) ;
102
+ for ( _, edge) in edge_list. iter ( ) {
81
103
let u = edge. source ( ) . index ( ) ;
82
104
let v = edge. target ( ) . index ( ) ;
83
105
if subgraphs. union ( u, v) {
84
- let w = edge. weight ( ) . clone_ref ( py) ;
85
- answer. push ( ( u, v, w) ) ;
106
+ mst_edges. push ( ( u, v, edge. weight ( ) . clone_ref ( py) ) ) ;
86
107
}
87
108
}
88
109
89
- Ok ( WeightedEdgeList { edges : answer } )
110
+ Ok ( WeightedEdgeList { edges : mst_edges } )
90
111
}
91
112
92
- /// Find the minimum spanning tree or forest of a graph
93
- /// using Kruskal's algorithm.
113
+ /// Find the minimum spanning tree or forest of an undirected graph using
114
+ /// Kruskal's algorithm.
94
115
///
95
- /// :param PyGraph graph: Undirected graph
96
- /// :param weight_fn: A callable object (function, lambda, etc) which
97
- /// will be passed the edge object and expected to return a ``float``. This
98
- /// tells rustworkx/rust how to extract a numerical weight as a ``float``
99
- /// for edge object. Some simple examples are::
116
+ /// A Minimum Spanning Tree (MST) is a subset of the edges of a connected,
117
+ /// undirected graph that connects all vertices together without cycles and
118
+ /// with the minimum possible total edge weight.
100
119
///
101
- /// minimum_spanning_tree(graph, weight_fn: lambda x: 1)
120
+ /// This function computes the edges that form the MST or forest of an
121
+ /// undirected graph. Kruskal's algorithm works by sorting all the edges in the
122
+ /// graph by their weights and then adding them one by one to the MST, ensuring
123
+ /// that no cycles are formed.
102
124
///
103
- /// to return a weight of 1 for all edges. Also::
125
+ /// >>> G = rx.PyGraph()
126
+ /// >>> G.add_nodes_from(["A", "B", "C", "D"])
127
+ /// NodeIndices[0, 1, 2, 3]
128
+ /// >>> G.add_edges_from([(0, 1, 10), (0, 2, 6), (0, 3, 5), (1, 3, 15), (2, 3, 4)])
129
+ /// EdgeIndices[0, 1, 2, 3, 4]
130
+ /// >>> mst_G = rx.minimum_spanning_tree(G, weight_fn=lambda x: x)
131
+ /// >>> mst_G.weighted_edge_list()
132
+ /// WeightedEdgeList[(2, 3, 4), (0, 3, 5), (0, 1, 10)]
104
133
///
105
- /// minimum_spanning_tree(graph, weight_fn: float)
134
+ /// In this example, the edge `(0, 2, 6)` won't become part of the MST because
135
+ /// in the moment it's considered, the two other edges connecting nodes
136
+ /// 0-3-2 are already parts of MST because of their lower weight.
106
137
///
107
- /// to cast the edge object as a float as the weight.
108
- /// :param float default_weight: If ``weight_fn`` isn't specified this optional
138
+ /// To obtain the result just as a list of edges, see :func:`~minimum_spanning_edges`.
139
+ ///
140
+ /// :param PyGraph graph: An undirected graph
141
+ /// :param weight_fn: A callable object (function, lambda, etc) that takes
142
+ /// an edge object and returns a ``float``. This function is used to
143
+ /// extract the numerical weight for each edge. For example:
144
+ ///
145
+ /// rx.minimum_spanning_tree(G, weight_fn=lambda x: 1)
146
+ ///
147
+ /// will assign a weight of 1 to all edges and thus ignore the real weights.
148
+ ///
149
+ /// rx.minimum_spanning_tree(G, weight_fn=float)
150
+ ///
151
+ /// will just cast the edge object to a ``float`` to determine its weight.
152
+ /// :param float default_weight: If ``weight_fn`` isn't specified, this optional
109
153
/// float value will be used for the weight/cost of each edge.
110
154
///
111
155
/// :returns: A Minimum Spanning Tree (or Forest, if the graph is not connected).
112
- ///
113
156
/// :rtype: PyGraph
114
157
///
115
158
/// .. note::
116
159
///
117
160
/// The new graph will keep the same node indices, but edge indices might differ.
161
+ ///
162
+ /// :raises ValueError: If a NaN value is found (or computed) as an edge weight.
118
163
#[ pyfunction]
119
164
#[ pyo3( signature=( graph, weight_fn=None , default_weight=1.0 ) , text_signature = "(graph, weight_fn=None, default_weight=1.0)" ) ]
120
165
pub fn minimum_spanning_tree (
@@ -126,11 +171,11 @@ pub fn minimum_spanning_tree(
126
171
let mut spanning_tree = ( * graph) . clone ( ) ;
127
172
spanning_tree. graph . clear_edges ( ) ;
128
173
129
- for edge in minimum_spanning_edges ( py, graph, weight_fn, default_weight) ?
174
+ for & ( u , v , ref weight ) in minimum_spanning_edges ( py, graph, weight_fn, default_weight) ?
130
175
. edges
131
176
. iter ( )
132
177
{
133
- spanning_tree. add_edge ( edge . 0 , edge . 1 , edge . 2 . clone_ref ( py) ) ?;
178
+ spanning_tree. add_edge ( u , v , weight . clone_ref ( py) ) ?;
134
179
}
135
180
136
181
Ok ( spanning_tree)
0 commit comments