Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
cliques.cc
Go to the documentation of this file.
1// Copyright 2010-2024 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <functional>
18#include <memory>
19#include <utility>
20#include <vector>
21
22#include "absl/container/flat_hash_set.h"
23
24namespace operations_research {
25namespace {
26// Encapsulates graph() to make all nodes self-connected.
27inline bool Connects(std::function<bool(int, int)> graph, int i, int j) {
28 return i == j || graph(i, j);
29}
30
31// Implements the recursive step of the Bron-Kerbosch algorithm with pivoting.
32// - graph is a callback such that graph->Run(i, j) returns true iff there is an
33// arc between i and j.
34// - callback is a callback called for all maximal cliques discovered by the
35// algorithm.
36// - input_candidates is an array that contains the list of nodes connected to
37// all nodes in the current clique. It is composed of two parts; the first
38// part contains the "not" set (nodes that were already processed and must not
39// be added to the clique - see the description of the algorithm in the
40// paper), and nodes that are candidates for addition. The candidates from the
41// "not" set are at the beginning of the array.
42// - first_candidate_index elements is the index of the first candidate that is
43// not in the "not" set (which is also the number of candidates in the "not"
44// set).
45// - num_input_candidates is the number of elements in input_candidates,
46// including both the "not" set and the actual candidates.
47// - current_clique is the current clique discovered by the algorithm.
48// - stop is a stopping condition for the algorithm; if the value it points to
49// is true, the algorithm stops further exploration and returns.
50// TODO(user) : rewrite this algorithm without recursion.
51void Search(std::function<bool(int, int)> graph,
52 std::function<bool(const std::vector<int>&)> callback,
53 int* input_candidates, int first_candidate_index,
54 int num_input_candidates, std::vector<int>* current_clique,
55 bool* stop) {
56 // The pivot is a node from input_candidates that is disconnected from the
57 // minimal number of nodes in the actual candidates (excluding the "not" set);
58 // the algorithm then selects only candidates that are disconnected from the
59 // pivot (and the pivot itself), to reach the termination condition as quickly
60 // as possible (see the original paper for more details).
61 int pivot = 0;
62
63 // A node that is disconnected from the selected pivot. This node is selected
64 // during the pivot matching phase to speed up the first iteration of the
65 // recursive call.
66 int disconnected_node = 0;
67
68 // The number of candidates (that are not in "not") disconnected from the
69 // selected pivot. The value is computed during pivot selection. In the
70 // "recursive" phase, we only need to do explore num_disconnected_candidates
71 // nodes, because after this step, all remaining candidates will all be
72 // connected to the pivot node (which is in "not"), so they can't form a
73 // maximal clique.
74 int num_disconnected_candidates = num_input_candidates;
75
76 // If the selected pivot is not in "not", we need to process one more
77 // candidate (the pivot itself). pre_increment is added to
78 // num_disconnected_candidates to compensate for this fact.
79 int pre_increment = 0;
80
81 // Find Pivot.
82 for (int i = 0; i < num_input_candidates && num_disconnected_candidates != 0;
83 ++i) {
84 int pivot_candidate = input_candidates[i];
85
86 // Count is the number of candidates (not including nodes in the "not" set)
87 // that are disconnected from the pivot candidate.
88 int count = 0;
89
90 // The index of a candidate node that is not connected to pivot_candidate.
91 // This node will be used to quickly start the nested iteration (we keep
92 // track of the index so that we don't have to find a node that is
93 // disconnected from the pivot later in the iteration).
94 int disconnected_node_candidate = 0;
95
96 // Compute the number of candidate nodes that are disconnected from
97 // pivot_candidate. Note that this computation is the same for the "not"
98 // candidates and the normal candidates.
99 for (int j = first_candidate_index;
100 j < num_input_candidates && count < num_disconnected_candidates; ++j) {
101 if (!Connects(graph, pivot_candidate, input_candidates[j])) {
102 count++;
103 disconnected_node_candidate = j;
104 }
105 }
106
107 // Update the pivot candidate if we found a new minimum for
108 // num_disconnected_candidates.
109 if (count < num_disconnected_candidates) {
110 pivot = pivot_candidate;
111 num_disconnected_candidates = count;
112
113 if (i < first_candidate_index) {
114 disconnected_node = disconnected_node_candidate;
115 } else {
116 disconnected_node = i;
117 // The pivot candidate is not in the "not" set. We need to pre-increment
118 // the counter for the node to compensate for that.
119 pre_increment = 1;
120 }
121 }
122 }
123
124 std::vector<int> new_candidates;
125 new_candidates.reserve(num_input_candidates);
126 for (int remaining_candidates = num_disconnected_candidates + pre_increment;
127 remaining_candidates >= 1; remaining_candidates--) {
128 // Swap a node that is disconnected from the pivot (or the pivot itself)
129 // with the first candidate, so that we can later move it to "not" simply by
130 // increasing the index of the first candidate that is not in "not".
131 const int selected = input_candidates[disconnected_node];
132 std::swap(input_candidates[disconnected_node],
133 input_candidates[first_candidate_index]);
134
135 // Fill the list of candidates and the "not" set for the recursive call:
136 new_candidates.clear();
137 for (int i = 0; i < first_candidate_index; ++i) {
138 if (Connects(graph, selected, input_candidates[i])) {
139 new_candidates.push_back(input_candidates[i]);
140 }
141 }
142 const int new_first_candidate_index = new_candidates.size();
143 for (int i = first_candidate_index + 1; i < num_input_candidates; ++i) {
144 if (Connects(graph, selected, input_candidates[i])) {
145 new_candidates.push_back(input_candidates[i]);
146 }
147 }
148 const int new_candidate_size = new_candidates.size();
149
150 // Add the selected candidate to the current clique.
151 current_clique->push_back(selected);
152
153 // If there are no remaining candidates, we have found a maximal clique.
154 // Otherwise, do the recursive step.
155 if (new_candidate_size == 0) {
156 *stop = callback(*current_clique);
157 } else {
158 if (new_first_candidate_index < new_candidate_size) {
159 Search(graph, callback, new_candidates.data(),
160 new_first_candidate_index, new_candidate_size, current_clique,
161 stop);
162 if (*stop) {
163 return;
164 }
165 }
166 }
167
168 // Remove the selected candidate from the current clique.
169 current_clique->pop_back();
170 // Add the selected candidate to the set "not" - we've already processed
171 // all possible maximal cliques that use this node in 'current_clique'. The
172 // current candidate is the element of the new candidate set, so we can move
173 // it to "not" simply by increasing first_candidate_index.
174 first_candidate_index++;
175
176 // Find the next candidate that is disconnected from the pivot.
177 if (remaining_candidates > 1) {
178 disconnected_node = first_candidate_index;
179 while (disconnected_node < num_input_candidates &&
180 Connects(graph, pivot, input_candidates[disconnected_node])) {
181 disconnected_node++;
182 }
183 }
184 }
185}
186
187class FindAndEliminate {
188 public:
189 FindAndEliminate(std::function<bool(int, int)> graph, int node_count,
190 std::function<bool(const std::vector<int>&)> callback)
191 : graph_(std::move(graph)),
192 node_count_(node_count),
193 callback_(std::move(callback)) {}
194
195 bool GraphCallback(int node1, int node2) {
196 if (visited_.find(
197 std::make_pair(std::min(node1, node2), std::max(node1, node2))) !=
198 visited_.end()) {
199 return false;
200 }
201 return Connects(graph_, node1, node2);
202 }
203
204 bool SolutionCallback(const std::vector<int>& solution) {
205 const int size = solution.size();
206 if (size > 1) {
207 for (int i = 0; i < size - 1; ++i) {
208 for (int j = i + 1; j < size; ++j) {
209 visited_.insert(std::make_pair(std::min(solution[i], solution[j]),
210 std::max(solution[i], solution[j])));
211 }
212 }
213 callback_(solution);
214 }
215 return false;
216 }
217
218 private:
219 std::function<bool(int, int)> graph_;
220 int node_count_;
221 std::function<bool(const std::vector<int>&)> callback_;
222 absl::flat_hash_set<std::pair<int, int>> visited_;
223};
224} // namespace
225
226// This method implements the 'version2' of the Bron-Kerbosch
227// algorithm to find all maximal cliques in a undirected graph.
228void FindCliques(std::function<bool(int, int)> graph, int node_count,
229 std::function<bool(const std::vector<int>&)> callback) {
230 std::unique_ptr<int[]> initial_candidates(new int[node_count]);
231 std::vector<int> actual;
232
233 for (int c = 0; c < node_count; ++c) {
234 initial_candidates[c] = c;
235 }
236
237 bool stop = false;
238 Search(std::move(graph), std::move(callback), initial_candidates.get(), 0,
239 node_count, &actual, &stop);
240}
241
242void CoverArcsByCliques(std::function<bool(int, int)> graph, int node_count,
243 std::function<bool(const std::vector<int>&)> callback) {
244 FindAndEliminate cache(std::move(graph), node_count, std::move(callback));
245 std::unique_ptr<int[]> initial_candidates(new int[node_count]);
246 std::vector<int> actual;
247
248 std::function<bool(int, int)> cached_graph = [&cache](int i, int j) {
249 return cache.GraphCallback(i, j);
250 };
251 std::function<bool(const std::vector<int>&)> cached_callback =
252 [&cache](const std::vector<int>& res) {
253 return cache.SolutionCallback(res);
254 };
255
256 for (int c = 0; c < node_count; ++c) {
257 initial_candidates[c] = c;
258 }
259
260 bool stop = false;
261 Search(std::move(cached_graph), std::move(cached_callback),
262 initial_candidates.get(), 0, node_count, &actual, &stop);
263}
264
265} // namespace operations_research
IntegerValue size
---------------— Search class --------------—
GraphType graph
MPCallback * callback
double solution
In SWIG mode, we don't want anything besides these top-level includes.
void FindCliques(std::function< bool(int, int)> graph, int node_count, std::function< bool(const std::vector< int > &)> callback)
Definition cliques.cc:228
void CoverArcsByCliques(std::function< bool(int, int)> graph, int node_count, std::function< bool(const std::vector< int > &)> callback)
Definition cliques.cc:242
STL namespace.