165 lines
5.4 KiB
C++
165 lines
5.4 KiB
C++
// Copyright 2010-2025 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.
|
|
|
|
//
|
|
// 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.
|
|
|
|
// The following uses disjoint-sets algorithms, see:
|
|
// https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Disjoint-set_forests
|
|
|
|
#include "ortools/graph/connected_components.h"
|
|
|
|
#include <algorithm>
|
|
#include <numeric>
|
|
#include <vector>
|
|
|
|
#include "ortools/base/stl_util.h"
|
|
|
|
void DenseConnectedComponentsFinder::SetNumberOfNodes(int num_nodes) {
|
|
const int old_num_nodes = GetNumberOfNodes();
|
|
if (num_nodes == old_num_nodes) {
|
|
return;
|
|
}
|
|
CHECK_GT(num_nodes, old_num_nodes);
|
|
// Each new node starts as an isolated component:
|
|
// It has itself as root.
|
|
parent_.resize(num_nodes);
|
|
std::iota(parent_.begin() + old_num_nodes, parent_.end(), old_num_nodes);
|
|
// It's in an isolated component of size 1.
|
|
component_size_.resize(num_nodes, 1);
|
|
// Its rank is 0.
|
|
rank_.resize(num_nodes);
|
|
// This introduces one extra component per added node.
|
|
num_components_ += num_nodes - old_num_nodes;
|
|
}
|
|
|
|
int DenseConnectedComponentsFinder::FindRoot(int node) {
|
|
DCHECK_GE(node, 0);
|
|
DCHECK_LT(node, GetNumberOfNodes());
|
|
|
|
// Search the root.
|
|
int root = parent_[node];
|
|
while (parent_[root] != root) {
|
|
root = parent_[root];
|
|
}
|
|
|
|
// Apply path compression.
|
|
while (node != root) {
|
|
const int prev_parent = parent_[node];
|
|
parent_[node] = root;
|
|
node = prev_parent;
|
|
}
|
|
return root;
|
|
}
|
|
|
|
const std::vector<int>& DenseConnectedComponentsFinder::GetComponentRoots() {
|
|
const int num_nodes = GetNumberOfNodes();
|
|
if (num_nodes != num_nodes_at_last_get_roots_call_) {
|
|
// Add potential roots for each new node that did not exist the last time
|
|
// GetComponentRoots() was called. The cost here is amortized against
|
|
// adding the nodes in the first place.
|
|
const int previous_num_roots = roots_.size();
|
|
roots_.resize(previous_num_roots + num_nodes -
|
|
num_nodes_at_last_get_roots_call_);
|
|
std::iota(roots_.begin() + previous_num_roots, roots_.end(),
|
|
num_nodes_at_last_get_roots_call_);
|
|
}
|
|
|
|
// Remove the roots that have been merged with other components. Each node
|
|
// only gets removed once from the roots vector, so the cost of FindRoot() is
|
|
// amortized against adding the edge.
|
|
gtl::STLEraseAllFromSequenceIf(
|
|
&roots_, [&](const int node) { return node != FindRoot(node); });
|
|
|
|
num_nodes_at_last_get_roots_call_ = num_nodes;
|
|
return roots_;
|
|
}
|
|
|
|
bool DenseConnectedComponentsFinder::AddEdge(int node1, int node2) {
|
|
// Grow if needed.
|
|
const int min_num_nodes = std::max(node1, node2) + 1;
|
|
if (min_num_nodes > GetNumberOfNodes()) {
|
|
SetNumberOfNodes(min_num_nodes);
|
|
}
|
|
|
|
// Just union the sets for node1 and node2.
|
|
int root1 = FindRoot(node1);
|
|
int root2 = FindRoot(node2);
|
|
|
|
// Already the same set.
|
|
if (root1 == root2) {
|
|
return false;
|
|
}
|
|
|
|
DCHECK_GE(num_components_, 2);
|
|
--num_components_;
|
|
|
|
const int component_size = component_size_[root1] + component_size_[root2];
|
|
|
|
// Attach the shallowest tree to root of the deepest one. Note that this
|
|
// operation grows the rank of the new common root by at most one (if the two
|
|
// trees originally have the same rank).
|
|
if (rank_[root1] > rank_[root2]) {
|
|
parent_[root2] = root1;
|
|
component_size_[root1] = component_size;
|
|
} else {
|
|
parent_[root1] = root2;
|
|
component_size_[root2] = component_size;
|
|
// If the ranks were the same then attaching just grew the rank by one.
|
|
if (rank_[root1] == rank_[root2]) {
|
|
++rank_[root2];
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool DenseConnectedComponentsFinder::Connected(int node1, int node2) {
|
|
if (node1 < 0 || node1 >= GetNumberOfNodes() || node2 < 0 ||
|
|
node2 >= GetNumberOfNodes()) {
|
|
return false;
|
|
}
|
|
return FindRoot(node1) == FindRoot(node2);
|
|
}
|
|
|
|
int DenseConnectedComponentsFinder::GetSize(int node) {
|
|
if (node < 0 || node >= GetNumberOfNodes()) {
|
|
return 0;
|
|
}
|
|
return component_size_[FindRoot(node)];
|
|
}
|
|
|
|
std::vector<int> DenseConnectedComponentsFinder::GetComponentIds() {
|
|
std::vector<int> component_ids(GetNumberOfNodes(), -1);
|
|
int current_component = 0;
|
|
for (int node = 0; node < GetNumberOfNodes(); ++node) {
|
|
int& root_component = component_ids[FindRoot(node)];
|
|
if (root_component < 0) {
|
|
// This is the first node in a yet unseen component.
|
|
root_component = current_component;
|
|
++current_component;
|
|
}
|
|
component_ids[node] = root_component;
|
|
}
|
|
return component_ids;
|
|
}
|