OR-Tools  9.6
topologicalsorter.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 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 <cstddef>
18 #include <cstdint>
19 #include <limits>
20 #include <map>
21 #include <queue>
22 #include <string>
23 #include <utility>
24 #include <vector>
25 
26 #include "absl/status/status.h"
27 #include "ortools/base/map_util.h"
28 #include "ortools/base/stl_util.h"
29 
30 namespace util {
31 namespace internal {
32 
33 namespace {
34 template <typename IntQueue>
35 inline void PopTop(IntQueue* q, int* top) {
36  *top = q->front();
37  q->pop();
38 }
39 
40 template <typename C, typename F>
41 void PopTop(std::priority_queue<int, C, F>* q, int* top) {
42  *top = q->top();
43  q->pop();
44 }
45 } // namespace
46 
47 template <bool stable_sort>
49  CHECK(!TraversalStarted()) << "Cannot add nodes after starting traversal";
50  CHECK_GE(node_index, 0) << "Index must not be negative";
51 
52  if (static_cast<std::size_t>(node_index) >= adjacency_lists_.size()) {
53  adjacency_lists_.resize(node_index + 1);
54  }
55 }
56 
57 // Up to a point, we detect duplicates up front and do not insert them.
58 // Then we switch to using RemoveDuplicates(), see below.
59 //
60 // Note(user): I did benchmarks on this in November 2011, and while
61 // 32 seemed too large, I did not see very significant performance
62 // differences with 0, 4, 8 or 16. But since larger values of this
63 // threshold mean that there will be slightly less space used up by
64 // small adjacency lists in case there are repeated edges, I picked 16.
66 
67 template <bool stable_sort>
69  const std::vector<std::pair<int, int>>& edges) {
70  CHECK(!TraversalStarted()) << "Cannot add edges after starting traversal";
71 
72  // Make a first pass to detect the number of nodes.
73  int max_node = -1;
74  for (const auto& [from, to] : edges) {
75  if (from > max_node) max_node = from;
76  if (to > max_node) max_node = to;
77  }
78  if (max_node >= 0) AddNode(max_node);
79 
80  // Make a second pass to reserve the adjacency list sizes.
81  // We use indegree_ as temporary node buffer to store the node out-degrees,
82  // since it isn't being used yet.
83  indegree_.assign(max_node + 1, 0);
84  for (const auto& [from, to] : edges) ++indegree_[from];
85  for (int node = 0; node < max_node; ++node) {
86  adjacency_lists_[node].reserve(indegree_[node]);
87  }
88  indegree_.clear();
89 
90  // Finally, add edges to the adjacency lists in a third pass. Don't bother
91  // doing the duplicate detection: in the bulk API, we assume that there isn't
92  // much edge duplication.
93  for (const auto& [from, to] : edges) adjacency_lists_[from].push_back(to);
94 }
95 
96 template <bool stable_sort>
98  CHECK(!TraversalStarted()) << "Cannot add edges after starting traversal";
99 
100  AddNode(std::max(from, to));
101 
102  AdjacencyList& adj_list = adjacency_lists_[from];
103  const uint32_t adj_list_size = adj_list.size();
104  if (adj_list_size <= kLazyDuplicateDetectionSizeThreshold) {
105  for (AdjacencyList::const_iterator it = adj_list.begin();
106  it != adj_list.end(); ++it) {
107  if (*it == to) {
108  return;
109  }
110  }
111  adj_list.push_back(to);
112  ++num_edges_;
113  } else {
114  adj_list.push_back(to);
115  if (++num_edges_added_since_last_duplicate_removal_ > ++num_edges_ / 2) {
116  num_edges_added_since_last_duplicate_removal_ = 0;
117  // We remove all duplicates at once, but skip lists for which the
118  // number of duplicates can't be too large, i.e. lists smaller than
119  // kLazyDuplicateDetectionSizeThreshold * 2. The overall ratio of
120  // duplicate edges remains bounded by 2/3 in the worst case.
121  num_edges_ -= RemoveDuplicates(&adjacency_lists_,
123  }
124  }
125 }
126 
127 template <bool stable_sort>
129  int* next_node_index, bool* cyclic, std::vector<int>* output_cycle_nodes) {
130  if (!TraversalStarted()) {
131  StartTraversal();
132  }
133 
134  *cyclic = false;
135  if (num_nodes_left_ == 0) {
136  return false;
137  }
138  if (nodes_with_zero_indegree_.empty()) {
139  VLOG(2) << "Not all nodes have been visited (" << num_nodes_left_
140  << " nodes left), but there aren't any zero-indegree nodes"
141  << " available. This graph is cyclic! Use ExtractCycle() for"
142  << " more information.";
143  *cyclic = true;
144  if (output_cycle_nodes != nullptr) {
145  ExtractCycle(output_cycle_nodes);
146  }
147  return false;
148  }
149 
150  // Pop one orphan node.
151  --num_nodes_left_;
152  PopTop(&nodes_with_zero_indegree_, next_node_index);
153 
154  // Swap out the adjacency list, since we won't need it afterwards,
155  // to decrease memory usage.
156  AdjacencyList adj_list;
157  adj_list.swap(adjacency_lists_[*next_node_index]);
158 
159  // Add new orphan nodes to nodes_with_zero_indegree_.
160  for (std::size_t i = 0; i < adj_list.size(); ++i) {
161  if (--indegree_[adj_list[i]] == 0) {
162  nodes_with_zero_indegree_.push(adj_list[i]);
163  }
164  }
165  return true;
166 }
167 
168 template <bool stable_sort>
170  if (TraversalStarted()) {
171  return;
172  }
173 
174  const int num_nodes = adjacency_lists_.size();
175  indegree_.assign(num_nodes, 0);
176 
177  // Iterate over all adjacency lists, and fill the indegree[] vector.
178  // Note that we don't bother removing duplicates: there can't be
179  // too many, since we removed them progressively, and it is actually
180  // cheaper to keep them at this point.
181  for (int from = 0; from < num_nodes; ++from) {
182  AdjacencyList& adj_list = adjacency_lists_[from];
183  for (AdjacencyList::const_iterator it = adj_list.begin();
184  it != adj_list.end(); ++it) {
185  ++indegree_[*it];
186  }
187  }
188 
189  // Initialize the nodes_with_zero_indegree_ vector.
190  for (int node = 0; node < num_nodes; ++node) {
191  if (indegree_[node] == 0) {
192  nodes_with_zero_indegree_.push(node);
193  }
194  }
195 
196  num_nodes_left_ = num_nodes;
197  traversal_started_ = true;
198 }
199 
200 // static
201 template <bool stable_sort>
203  std::vector<AdjacencyList>* lists, int skip_lists_smaller_than) {
204  // We can always skip lists with less than 2 elements.
205  if (skip_lists_smaller_than < 2) {
206  skip_lists_smaller_than = 2;
207  }
208  const int n = lists->size();
209  std::vector<bool> visited(n, false);
210  int num_duplicates_removed = 0;
211  for (std::vector<AdjacencyList>::iterator list = lists->begin();
212  list != lists->end(); ++list) {
213  if (list->size() < static_cast<std::size_t>(skip_lists_smaller_than)) {
214  continue;
215  }
216  num_duplicates_removed += list->size();
217  // To optimize the duplicate removal loop, we split it in two:
218  // first, find the first duplicate, then copy the rest of the shifted
219  // adjacency list as we keep detecting duplicates.
220  AdjacencyList::iterator it = list->begin();
221  DCHECK(it != list->end());
222  while (!visited[*it]) {
223  visited[*(it++)] = true;
224  if (it == list->end()) {
225  break;
226  }
227  }
228  // Skip the shifted copy if there were no duplicates at all.
229  if (it != list->end()) {
230  AdjacencyList::iterator it2 = it;
231  while (++it != list->end()) {
232  if (!visited[*it]) {
233  visited[*it] = true;
234  *(it2++) = *it;
235  }
236  }
237  list->erase(it2, list->end());
238  }
239  for (it = list->begin(); it != list->end(); ++it) {
240  visited[*it] = false;
241  }
242  num_duplicates_removed -= list->size();
243  }
244  return num_duplicates_removed;
245 }
246 
247 // Note(user): as of 2012-09, this implementation works in
248 // O(number of edges + number of nodes), which is the theoretical best.
249 // It could probably be optimized to gain a significant constant speed-up;
250 // but at the cost of more code complexity.
251 template <bool stable_sort>
253  std::vector<int>* cycle_nodes) const {
254  *cycle_nodes = util::graph::FindCycleInGraph(adjacency_lists_).value();
255 }
256 
257 // Generate the templated code. Including these definitions allows us
258 // to have templated code inside the .cc file and not incur linker errors.
261 
262 } // namespace internal
263 } // namespace util
int64_t max
Definition: alldiff_cst.cc:140
void ExtractCycle(std::vector< int > *cycle_nodes) const
absl::InlinedVector< int, 4 > AdjacencyList
static int RemoveDuplicates(std::vector< AdjacencyList > *lists, int skip_lists_smaller_than)
void AddEdges(const std::vector< std::pair< int, int >> &edges)
bool GetNext(int *next_node_index, bool *cyclic, std::vector< int > *output_cycle_nodes=nullptr)
absl::StatusOr< std::vector< int > > FindCycleInGraph(const AdjacencyLists &adj)
static const int kLazyDuplicateDetectionSizeThreshold
#define VLOG(verboselevel)
Definition: vlog.h:39