// Copyright 2010-2018 Google LLC // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Graph connectivity algorithm for undirected graphs. // Memory consumption: O(n) where m is the number of arcs and n the number // of nodes. // TODO(user): add depth-first-search based connectivity for directed graphs. // TODO(user): add depth-first-search based biconnectivity for directed graphs. #ifndef OR_TOOLS_GRAPH_CONNECTIVITY_H_ #define OR_TOOLS_GRAPH_CONNECTIVITY_H_ #include #include "ortools/base/integral_types.h" #include "ortools/base/logging.h" #include "ortools/base/macros.h" namespace operations_research { // Template class implementing a Union-Find algorithm with path compression for // maintaining the connected components of a graph. // See Cormen et al. 2nd Edition. MIT Press, 2001. ISBN 0-262-03293-7. // Chapter 21: Data structures for Disjoint Sets, pp. 498-524. // and Tarjan (1975). Efficiency of a Good But Not Linear Set // Union Algorithm. Journal of the ACM 22(2):215-225 // It is implemented as a template so that the size of NodeIndex can be chosen // depending on the size of the graphs considered. // The main interest is that arcs do not need to be kept. Thus the memory // complexity is O(n) where n is the number of nodes in the graph. // The complexity of this algorithm is O(n . alpha(n)) where alpha(n) is // the inverse Ackermann function. alpha(n) <= log(log(log(..log(log(n))..) // In practice alpha(n) <= 5. // See Tarjan and van Leeuwen (1984). Worst-case analysis of set union // algorithms. Journal of the ACM 31(2):245-281. // // Usage example: // ConnectedComponents components; // components.Init(num_nodes); // for (int arc = 0; arc < num_arcs; ++arc) { // components.AddArc(tail[arc], head[arc]); // } // int num_connected_components = components.GetNumberOfConnectedComponents(); // if (num_connected_components == 1) { // // Graph is completely connected. // } // // Group the nodes in the same connected component together. // // group[class_number][i] contains the i-th node in group class_number. // hash_map > group(num_connected_components); // for (int node = 0; node < num_nodes; ++node) { // group[components.GetClassRepresentative(node)].push_back(node); // } // // Keywords: graph, connected components. template class ConnectedComponents { public: ConnectedComponents() : num_nodes_(0), class_(), class_size_() {} // Reserves memory for num_nodes and resets the data structures. void Init(NodeIndex num_nodes) { CHECK_GE(num_nodes, 0); num_nodes_ = num_nodes; class_.resize(num_nodes_); class_size_.assign(num_nodes_, 1); for (NodeIndex node = 0; node < num_nodes_; ++node) { class_[node] = node; } } // Adds the information that NodeIndex tail and NodeIndex head are connected. void AddArc(NodeIndex tail, NodeIndex head) { const NodeIndex tail_class = CompressPath(tail); const NodeIndex head_class = CompressPath(head); if (tail_class != head_class) { MergeClasses(tail_class, head_class); } } // Adds a complete StarGraph to the object. Note that Depth-First Search // is a better algorithm for finding connected components on graphs. // TODO(user): implement Depth-First Search-based connected components finder. template void AddGraph(const Graph& graph) { Init(graph.num_nodes()); for (NodeIndex tail = 0; tail < graph.num_nodes(); ++tail) { for (typename Graph::OutgoingArcIterator it(graph, tail); it.Ok(); it.Next()) { AddArc(tail, graph.Head(it.Index())); } } } // Compresses the path for node. NodeIndex CompressPath(NodeIndex node) { CheckNodeBounds(node); NodeIndex parent = node; while (parent != class_[parent]) { CheckNodeBounds(class_[parent]); CheckNodeBounds(class_[class_[parent]]); parent = class_[parent]; } while (node != class_[node]) { const NodeIndex old_parent = class_[node]; class_[node] = parent; node = old_parent; } return parent; } // Returns the equivalence class representative for node. NodeIndex GetClassRepresentative(NodeIndex node) { return CompressPath(node); } // Returns the number of connected components. Allocates num_nodes_ bits for // the computation. NodeIndex GetNumberOfConnectedComponents() { NodeIndex number = 0; for (NodeIndex node = 0; node < num_nodes_; ++node) { if (class_[node] == node) ++number; } return number; } // Merges the equivalence classes of node1 and node2. void MergeClasses(NodeIndex node1, NodeIndex node2) { // It's faster (~10%) to swap the two values and have a single piece of // code for merging the classes. CheckNodeBounds(node1); CheckNodeBounds(node2); if (class_size_[node1] < class_size_[node2]) { std::swap(node1, node2); } class_[node2] = node1; class_size_[node1] += class_size_[node2]; } private: void CheckNodeBounds(NodeIndex node_index) { DCHECK_LE(0, node_index); DCHECK_LT(node_index, num_nodes_); } // The exact number of nodes in the graph. NodeIndex num_nodes_; // The equivalence class representative for each node. std::vector class_; // The size of each equivalence class of each node. Used to compress the paths // and therefore achieve better time complexity. std::vector class_size_; DISALLOW_COPY_AND_ASSIGN(ConnectedComponents); }; } // namespace operations_research #endif // OR_TOOLS_GRAPH_CONNECTIVITY_H_