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