Google OR-Tools v9.14
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
dag_connectivity.cc
Go to the documentation of this file.
1// Copyright 2010-2025 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 <cstdint>
18#include <utility>
19#include <vector>
20
21#include "absl/types/span.h"
25
26namespace operations_research {
27
28// The algorithm is as follows:
29// 1. Sort the nodes of the graph topologically. If a cycle is detected,
30// terminate
31// 2. Build the adjacency list for the graph, i.e., adj_list[i] is the list
32// of nodes that can be directly reached from i.
33// 3. Create a 2d bool vector x where x[i][j] indicates there is a path from
34// i to j, and for each arc in "arcs", set x[i][j] to true
35// 4. In reverse topological order (leaves first) for each node i, for each
36// child j of i, for each node k reachable for j, set k to be reachable
37// from i as well (x[i][k] = true for all k s.t. x[j][k] is true).
38//
39// The running times of the steps are:
40// 1. O(num_arcs)
41// 2. O(num_arcs)
42// 3. O(num_nodes^2 + num_arcs)
43// 4. O(num_nodes*num_arcs)
44// Thus the total run time is O(num_nodes^2 + num_nodes*num_arcs).
45//
46// Implementation note: typically, step 4 will dominate. To speed up the inner
47// loop, we use Bitset64, allowing use to merge 64 x[k][j] values at a time with
48// the |= operator.
49//
50// For graphs where num_arcs is o(num_nodes), a different data structure could
51// be used in 3, but this isn't really the interesting case (and prevents |=).
52//
53// A further improvement on this algorithm is possible, step four can run in
54// time O(num_nodes*num_arcs_in_transitive_reduction), and as a by product,
55// the transitive reduction can also be produced as output. For details, see
56// "A REDUCT-AND_CLOSURE ALGORITHM FOR GRAPHS" (Alla Goralcikova and
57// Vaclav Koubek 1979). The better typeset paper "AN IMPROVED ALGORITHM FOR
58// TRANSITIVE CLOSURE ON ACYCLIC DIGRAPHS" (Klaus Simon 1988) gives a slight
59// improvement on the result (less memory, same runtime).
60std::vector<Bitset64<int64_t>> ComputeDagConnectivity(
61 absl::Span<const std::pair<int, int>> arcs, bool* error_was_cyclic,
62 std::vector<int>* error_cycle_out) {
63 CHECK(error_was_cyclic != nullptr);
64 CHECK(error_cycle_out != nullptr);
65 *error_was_cyclic = false;
66 error_cycle_out->clear();
67 if (arcs.empty()) return {};
68 int num_nodes = 0;
69 for (const std::pair<int, int>& arc : arcs) {
70 CHECK_GE(arc.first, 0);
71 CHECK_GE(arc.second, 0);
72 num_nodes = std::max(num_nodes, arc.first + 1);
73 num_nodes = std::max(num_nodes, arc.second + 1);
74 }
75 DenseIntStableTopologicalSorter sorter(num_nodes);
76 for (const auto& arc : arcs) {
77 sorter.AddEdge(arc.first, arc.second);
78 }
79 std::vector<int> topological_order;
80 int next;
81 while (sorter.GetNext(&next, error_was_cyclic, error_cycle_out)) {
82 topological_order.push_back(next);
83 }
84 if (*error_was_cyclic) return {};
85 std::vector<std::vector<int>> adjacency_list(num_nodes);
86 for (const auto& arc : arcs) {
87 adjacency_list[arc.first].push_back(arc.second);
88 }
89
90 std::vector<Bitset64<int64_t>> connectivity(num_nodes);
91 for (Bitset64<int64_t>& bitset : connectivity) {
92 bitset.Resize(num_nodes);
93 }
94 for (const auto& arc : arcs) {
95 connectivity[arc.first].Set(arc.second);
96 }
97
98 // Iterate over the nodes in reverse topological order.
99 std::reverse(topological_order.begin(), topological_order.end());
100 // NOTE(user): these two loops visit every arc in the graph, and each
101 // union is over a set of size given by the number of nodes. This gives the
102 // runtime in step 4 of O(num_nodes*num_arcs)
103 for (const int node : topological_order) {
104 for (const int child : adjacency_list[node]) {
105 connectivity[node].Union(connectivity[child]);
106 }
107 }
108 return connectivity;
109}
110
111std::vector<Bitset64<int64_t>> ComputeDagConnectivityOrDie(
112 absl::Span<const std::pair<int, int>> arcs) {
113 bool error_was_cyclic = false;
114 std::vector<int> error_cycle;
115 std::vector<Bitset64<int64_t>> result =
116 ComputeDagConnectivity(arcs, &error_was_cyclic, &error_cycle);
117 CHECK(!error_was_cyclic) << "Graph should have been acyclic but has cycle: "
118 << gtl::LogContainer(error_cycle);
119 return result;
120}
121
122} // namespace operations_research
bool GetNext(int *next_node_index, bool *cyclic, std::vector< int > *output_cycle_nodes=nullptr)
auto LogContainer(const ContainerT &container, const PolicyT &policy) -> decltype(gtl::LogRange(container.begin(), container.end(), policy))
In SWIG mode, we don't want anything besides these top-level includes.
std::vector< Bitset64< int64_t > > ComputeDagConnectivity(absl::Span< const std::pair< int, int > > arcs, bool *error_was_cyclic, std::vector< int > *error_cycle_out)
std::vector< Bitset64< int64_t > > ComputeDagConnectivityOrDie(absl::Span< const std::pair< int, int > > arcs)
::util::DenseIntStableTopologicalSorter DenseIntStableTopologicalSorter