@@ -27,28 +27,53 @@ use rayon::prelude::*;
2727
2828use crate :: iterators:: WeightedEdgeList ;
2929
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
3131/// using Kruskal's algorithm.
3232///
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.
3836///
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.
4041///
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)]
4249///
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.
4453///
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
4769/// float value will be used for the weight/cost of each edge.
4870///
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.
5174/// :rtype: WeightedEdgeList
75+ ///
76+ /// :raises ValueError: If a NaN value is found (or computed) as an edge weight.
5277#[ pyfunction]
5378#[ pyo3( signature=( graph, weight_fn=None , default_weight=1.0 ) , text_signature = "(graph, weight_fn=None, default_weight=1.0)" ) ]
5479pub fn minimum_spanning_edges (
@@ -69,52 +94,72 @@ pub fn minimum_spanning_edges(
6994 edge_list. push ( ( weight, edge) ) ;
7095 }
7196
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 )
7699 } ) ;
77100
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 ( ) {
81103 let u = edge. source ( ) . index ( ) ;
82104 let v = edge. target ( ) . index ( ) ;
83105 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) ) ) ;
86107 }
87108 }
88109
89- Ok ( WeightedEdgeList { edges : answer } )
110+ Ok ( WeightedEdgeList { edges : mst_edges } )
90111}
91112
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.
94115///
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.
100119///
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.
102124///
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)]
104133///
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.
106137///
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
109153/// float value will be used for the weight/cost of each edge.
110154///
111155/// :returns: A Minimum Spanning Tree (or Forest, if the graph is not connected).
112- ///
113156/// :rtype: PyGraph
114157///
115158/// .. note::
116159///
117160/// 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.
118163#[ pyfunction]
119164#[ pyo3( signature=( graph, weight_fn=None , default_weight=1.0 ) , text_signature = "(graph, weight_fn=None, default_weight=1.0)" ) ]
120165pub fn minimum_spanning_tree (
@@ -126,11 +171,11 @@ pub fn minimum_spanning_tree(
126171 let mut spanning_tree = ( * graph) . clone ( ) ;
127172 spanning_tree. graph . clear_edges ( ) ;
128173
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) ?
130175 . edges
131176 . iter ( )
132177 {
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) ) ?;
134179 }
135180
136181 Ok ( spanning_tree)
0 commit comments