graph: Export from google3

* Add rooted_tree.h
* Add few samples
* Clean includes
This commit is contained in:
Corentin Le Molgat
2024-05-28 16:52:08 +02:00
parent 5c527f40d5
commit dc1e278562
31 changed files with 2174 additions and 91 deletions

View File

@@ -93,6 +93,7 @@ cc_library(
"//ortools/base:iterator_adaptors",
"//ortools/base:threadpool",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/log",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/synchronization",
],
@@ -121,6 +122,7 @@ cc_library(
"//ortools/util:bitset",
"//ortools/util:saturated_arithmetic",
"//ortools/util:vector_or_function",
"@com_google_absl//absl/types:span",
],
)
@@ -167,8 +169,9 @@ cc_library(
hdrs = ["one_tree_lower_bound.h"],
deps = [
":christofides",
":graph",
":minimum_spanning_tree",
"//ortools/base:types",
"@com_google_absl//absl/log",
"@com_google_absl//absl/types:span",
],
)
@@ -265,7 +268,6 @@ cc_test(
"//ortools/base:path",
"//ortools/linear_solver",
"//ortools/util:file_util",
"@com_google_absl//absl/flags:flag",
"@com_google_absl//absl/random",
"@com_google_absl//absl/strings:str_format",
"@com_google_benchmark//:benchmark",
@@ -363,10 +365,8 @@ cc_library(
"//ortools/base:adjustable_priority_queue",
"//ortools/base:int_type",
"//ortools/base:strong_vector",
"//ortools/base:types",
"//ortools/util:saturated_arithmetic",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/strings",
],
)
@@ -390,7 +390,6 @@ cc_library(
cc_library(
name = "dag_constrained_shortest_path",
testonly = True,
srcs = ["dag_constrained_shortest_path.cc"],
hdrs = ["dag_constrained_shortest_path.h"],
deps = [
@@ -407,6 +406,21 @@ cc_library(
],
)
cc_library(
name = "rooted_tree",
hdrs = ["rooted_tree.h"],
deps = [
"//ortools/base:status_macros",
"@com_google_absl//absl/algorithm:container",
"@com_google_absl//absl/log",
"@com_google_absl//absl/log:check",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/types:span",
],
)
# From util/graph
cc_library(
name = "connected_components",

View File

@@ -27,13 +27,14 @@
#define OR_TOOLS_GRAPH_CHRISTOFIDES_H_
#include <cstdint>
#include <functional>
#include <string>
#include <utility>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "ortools/base/logging.h"
#include "ortools/base/types.h"
#include "ortools/graph/eulerian_path.h"
#include "ortools/graph/graph.h"
#include "ortools/graph/minimum_spanning_tree.h"

View File

@@ -188,7 +188,9 @@ class FindAndEliminate {
public:
FindAndEliminate(std::function<bool(int, int)> graph, int node_count,
std::function<bool(const std::vector<int>&)> callback)
: graph_(graph), node_count_(node_count), callback_(callback) {}
: graph_(std::move(graph)),
node_count_(node_count),
callback_(std::move(callback)) {}
bool GraphCallback(int node1, int node2) {
if (visited_.find(
@@ -233,13 +235,13 @@ void FindCliques(std::function<bool(int, int)> graph, int node_count,
}
bool stop = false;
Search(graph, callback, initial_candidates.get(), 0, node_count, &actual,
&stop);
Search(std::move(graph), std::move(callback), initial_candidates.get(), 0,
node_count, &actual, &stop);
}
void CoverArcsByCliques(std::function<bool(int, int)> graph, int node_count,
std::function<bool(const std::vector<int>&)> callback) {
FindAndEliminate cache(graph, node_count, callback);
FindAndEliminate cache(std::move(graph), node_count, std::move(callback));
std::unique_ptr<int[]> initial_candidates(new int[node_count]);
std::vector<int> actual;
@@ -256,8 +258,8 @@ void CoverArcsByCliques(std::function<bool(int, int)> graph, int node_count,
}
bool stop = false;
Search(cached_graph, cached_callback, initial_candidates.get(), 0, node_count,
&actual, &stop);
Search(std::move(cached_graph), std::move(cached_callback),
initial_candidates.get(), 0, node_count, &actual, &stop);
}
} // namespace operations_research

View File

@@ -24,6 +24,7 @@
#ifndef OR_TOOLS_GRAPH_CLIQUES_H_
#define OR_TOOLS_GRAPH_CLIQUES_H_
#include <cstddef>
#include <cstdint>
#include <functional>
#include <limits>

View File

@@ -14,8 +14,6 @@
#ifndef OR_TOOLS_GRAPH_DAG_CONSTRAINED_SHORTEST_PATH_H_
#define OR_TOOLS_GRAPH_DAG_CONSTRAINED_SHORTEST_PATH_H_
#include <stdbool.h>
#include <cmath>
#include <limits>
#include <vector>
@@ -165,6 +163,7 @@ class ConstrainedShortestPathsOnDagWrapper {
std::vector<double>& lengths_from_sources,
std::vector<std::vector<double>>& resources_from_sources,
std::vector<ArcIndex>& incoming_arc_indices_from_sources,
std::vector<int>& incoming_label_indices_from_sources,
std::vector<int>& first_label, std::vector<int>& num_labels);
// Returns the arc index linking two nodes from each pass forming the best
@@ -184,12 +183,9 @@ class ConstrainedShortestPathsOnDagWrapper {
// `sources` (if `direction` iS FORWARD) or `destinations` (if `direction` is
// BACKWARD) and ends in node represented by `best_label_index`.
std::vector<ArcIndex> ArcPathTo(
int best_label_index, const GraphType& reverse_graph,
absl::Span<const double> arc_lengths,
absl::Span<const double> lengths_from_sources,
int best_label_index,
absl::Span<const ArcIndex> incoming_arc_indices_from_sources,
absl::Span<const int> first_label,
absl::Span<const int> num_labels) const;
absl::Span<const int> incoming_label_indices_from_sources) const;
// Returns the list of all the nodes implied by a given `arc_path`.
std::vector<NodeIndex> NodePathImpliedBy(absl::Span<const ArcIndex> arc_path,
@@ -257,6 +253,7 @@ class ConstrainedShortestPathsOnDagWrapper {
std::vector<double> lengths_from_sources_[2];
std::vector<std::vector<double>> resources_from_sources_[2];
std::vector<ArcIndex> incoming_arc_indices_from_sources_[2];
std::vector<int> incoming_label_indices_from_sources_[2];
std::vector<int> node_first_label_[2];
std::vector<int> node_num_labels_[2];
};
@@ -560,6 +557,8 @@ PathWithLength ConstrainedShortestPathsOnDagWrapper<
/*resources_from_sources=*/resources_from_sources_[dir],
/*incoming_arc_indices_from_sources=*/
incoming_arc_indices_from_sources_[dir],
/*incoming_label_indices_from_sources=*/
incoming_label_indices_from_sources_[dir],
/*first_label=*/node_first_label_[dir],
/*num_labels=*/node_num_labels_[dir]);
});
@@ -608,13 +607,10 @@ PathWithLength ConstrainedShortestPathsOnDagWrapper<
for (const Direction dir : {FORWARD, BACKWARD}) {
for (const ArcIndex sub_arc_index : ArcPathTo(
/*best_label_index=*/best_label_pair.label_index[dir],
/*reverse_graph=*/sub_reverse_graph_[dir],
/*arc_lengths=*/sub_arc_lengths[dir],
/*lengths_from_sources=*/lengths_from_sources_[dir],
/*incoming_arc_indices_from_sources=*/
incoming_arc_indices_from_sources_[dir],
/*first_label=*/node_first_label_[dir],
/*num_labels=*/node_num_labels_[dir])) {
/*incoming_label_indices_from_sources=*/
incoming_label_indices_from_sources_[dir])) {
const ArcIndex arc_index = sub_full_arc_indices_[dir][sub_arc_index];
if (arc_index == -1) {
break;
@@ -634,6 +630,7 @@ PathWithLength ConstrainedShortestPathsOnDagWrapper<
resources_from_sources_[dir][r].clear();
}
incoming_arc_indices_from_sources_[dir].clear();
incoming_label_indices_from_sources_[dir].clear();
}
return {.length = best_label_pair.length,
.arc_path = arc_path,
@@ -654,6 +651,7 @@ void ConstrainedShortestPathsOnDagWrapper<GraphType>::
std::vector<double>& lengths_from_sources,
std::vector<std::vector<double>>& resources_from_sources,
std::vector<ArcIndex>& incoming_arc_indices_from_sources,
std::vector<int>& incoming_label_indices_from_sources,
std::vector<int>& first_label, std::vector<int>& num_labels) {
// Initialize source node.
const NodeIndex source_node = reverse_graph.num_nodes() - 1;
@@ -664,10 +662,12 @@ void ConstrainedShortestPathsOnDagWrapper<GraphType>::
resources_from_sources[r].push_back(0);
}
incoming_arc_indices_from_sources.push_back(-1);
incoming_label_indices_from_sources.push_back(-1);
std::vector<double> lengths_to;
std::vector<std::vector<double>> resources_to(num_resources_);
std::vector<ArcIndex> incoming_arc_indices_to;
std::vector<int> incoming_label_indices_to;
std::vector<int> label_indices_to;
std::vector<double> resources(num_resources_);
for (NodeIndex to = 0; to < source_node; ++to) {
@@ -676,6 +676,7 @@ void ConstrainedShortestPathsOnDagWrapper<GraphType>::
resources_to[r].clear();
}
incoming_arc_indices_to.clear();
incoming_label_indices_to.clear();
for (const ArcIndex reverse_arc_index : reverse_graph.OutgoingArcs(to)) {
const NodeIndex from = reverse_graph.Head(reverse_arc_index);
const double arc_length = arc_lengths[reverse_arc_index];
@@ -703,6 +704,7 @@ void ConstrainedShortestPathsOnDagWrapper<GraphType>::
resources_to[r].push_back(resources[r]);
}
incoming_arc_indices_to.push_back(reverse_arc_index);
incoming_label_indices_to.push_back(label_index);
}
}
// Sort labels lexicographically with lengths then resources.
@@ -753,6 +755,8 @@ void ConstrainedShortestPathsOnDagWrapper<GraphType>::
}
incoming_arc_indices_from_sources.push_back(
incoming_arc_indices_to[label_i_index]);
incoming_label_indices_from_sources.push_back(
incoming_label_indices_to[label_i_index]);
++num_labels_to;
if (lengths_from_sources.size() >= max_num_created_labels) {
return;
@@ -857,37 +861,19 @@ template <class GraphType>
#endif
std::vector<typename GraphType::ArcIndex>
ConstrainedShortestPathsOnDagWrapper<GraphType>::ArcPathTo(
const int best_label_index, const GraphType& reverse_graph,
absl::Span<const double> arc_lengths,
absl::Span<const double> lengths_from_sources,
const int best_label_index,
absl::Span<const ArcIndex> incoming_arc_indices_from_sources,
absl::Span<const int> first_label, absl::Span<const int> num_labels) const {
if (best_label_index == -1) {
return {};
}
absl::Span<const int> incoming_label_indices_from_sources) const {
int current_label_index = best_label_index;
std::vector<ArcIndex> arc_path;
for (int i = 0; i < reverse_graph.num_nodes(); ++i) {
const ArcIndex current_arc_index =
incoming_arc_indices_from_sources[current_label_index];
if (current_arc_index == -1) {
for (int i = 0; i < graph_->num_nodes(); ++i) {
if (current_label_index == -1) {
break;
}
arc_path.push_back(current_arc_index);
const NodeIndex sub_node = reverse_graph.Head(current_arc_index);
const double current_length = lengths_from_sources[current_label_index];
for (int label_index = first_label[sub_node];
label_index < first_label[sub_node] + num_labels[sub_node];
++label_index) {
if (std::abs(lengths_from_sources[label_index] +
arc_lengths[current_arc_index] - current_length) <=
kTolerance) {
current_label_index = label_index;
break;
}
}
arc_path.push_back(incoming_arc_indices_from_sources[current_label_index]);
current_label_index =
incoming_label_indices_from_sources[current_label_index];
}
CHECK_EQ(incoming_arc_indices_from_sources[current_label_index], -1);
return arc_path;
}

View File

@@ -44,7 +44,7 @@ ShortestPathOnDagProblem ReadProblem(
std::vector<double> arc_lengths;
arc_lengths.reserve(arcs_with_length.size());
for (const auto& arc : arcs_with_length) {
graph.AddArc(arc.tail, arc.head);
graph.AddArc(arc.from, arc.to);
arc_lengths.push_back(arc.length);
}
std::vector<ArcIndex> permutation;
@@ -58,17 +58,18 @@ ShortestPathOnDagProblem ReadProblem(
}
}
const absl::StatusOr<std::vector<NodeIndex>> topological_order =
absl::StatusOr<std::vector<NodeIndex>> topological_order =
util::graph::FastTopologicalSort(graph);
CHECK_OK(topological_order) << "arcs_with_length form a cycle.";
return ShortestPathOnDagProblem{.graph = graph,
.arc_lengths = arc_lengths,
.original_arc_indices = original_arc_indices,
.topological_order = *topological_order};
return ShortestPathOnDagProblem{
.graph = std::move(graph),
.arc_lengths = std::move(arc_lengths),
.original_arc_indices = std::move(original_arc_indices),
.topological_order = std::move(topological_order).value()};
}
void GetOriginalArcPath(const std::vector<ArcIndex>& original_arc_indices,
void GetOriginalArcPath(absl::Span<const ArcIndex> original_arc_indices,
std::vector<ArcIndex>& arc_path) {
if (original_arc_indices.empty()) {
return;
@@ -98,7 +99,7 @@ PathWithLength ShortestPathsOnDag(
GetOriginalArcPath(problem.original_arc_indices, arc_path);
return PathWithLength{
.length = shortest_path_on_dag.LengthTo(destination),
.arc_path = arc_path,
.arc_path = std::move(arc_path),
.node_path = shortest_path_on_dag.NodePathTo(destination)};
}

View File

@@ -49,11 +49,11 @@ namespace operations_research {
// Basic API.
// -----------------------------------------------------------------------------
// `tail` and `head` should both be in [0, num_nodes)
// `from` and `to` should both be in [0, num_nodes).
// If the length is +inf, then the arc should not be used.
struct ArcWithLength {
int tail = 0;
int head = 0;
int from = 0;
int to = 0;
double length = 0.0;
};

View File

@@ -177,7 +177,6 @@
#include "absl/strings/str_cat.h"
#include "ortools/base/logging.h"
#include "ortools/base/types.h"
#include "ortools/util/permutation.h"
#include "ortools/util/zvector.h"

View File

@@ -77,21 +77,19 @@
// Keywords: Traveling Salesman, Hamiltonian Path, Dynamic Programming,
// Held, Karp.
#include <math.h>
#include <stddef.h>
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <limits>
#include <memory>
#include <stack>
#include <type_traits>
#include <utility>
#include <vector>
#include "absl/types/span.h"
#include "ortools/base/logging.h"
#include "ortools/base/types.h"
#include "ortools/util/bitset.h"
#include "ortools/util/saturated_arithmetic.h"
#include "ortools/util/vector_or_function.h"
@@ -562,7 +560,7 @@ class HamiltonianPathSolver {
std::vector<int> ComputePath(CostType cost, NodeSet set, int end);
// Returns true if the path covers all nodes, and its cost is equal to cost.
bool PathIsValid(const std::vector<int>& path, CostType cost);
bool PathIsValid(absl::Span<const int> path, CostType cost);
// Cost function used to build Hamiltonian paths.
MatrixOrFunction<CostType, CostFunction, true> cost_;
@@ -765,7 +763,7 @@ std::vector<int> HamiltonianPathSolver<CostType, CostFunction>::ComputePath(
template <typename CostType, typename CostFunction>
bool HamiltonianPathSolver<CostType, CostFunction>::PathIsValid(
const std::vector<int>& path, CostType cost) {
absl::Span<const int> path, CostType cost) {
NodeSet coverage(0);
for (int node : path) {
coverage = coverage.AddElement(node);

View File

@@ -302,7 +302,9 @@ KShortestPaths YenKShortestPaths(const GraphType& graph,
std::priority_queue<internal::PathWithPriority>>
variant_path_queue;
for (; k > 0; --k) {
// One path has already been generated (the shortest one). Only k-1 more
// paths need to be generated.
for (; k > 1; --k) {
// Generate variant paths from the last shortest path.
const absl::Span<NodeIndex> last_shortest_path =
absl::MakeSpan(paths.paths.back());

View File

@@ -164,6 +164,24 @@ TEST(KShortestPathsYenTest, HasTwoPathsWithLongerPath) {
EXPECT_THAT(paths.distances, ElementsAre(4, 30));
}
TEST(KShortestPathsYenTest, HasThreePathsbutKIsTwo) {
StaticGraph<> graph;
graph.AddArc(0, 1);
graph.AddArc(0, 2);
graph.AddArc(0, 3);
graph.AddArc(1, 2);
graph.AddArc(3, 2);
(void)graph.Build();
std::vector<PathDistance> lengths{1, 1, 1, 1, 1};
const KShortestPaths paths = YenKShortestPaths(graph, lengths, /*source=*/0,
/*destination=*/2, /*k=*/2);
EXPECT_THAT(paths.paths,
ElementsAre(std::vector<int>{0, 2}, std::vector<int>{0, 1, 2}));
EXPECT_THAT(paths.distances, ElementsAre(1, 2));
}
// TODO(user): randomized tests? Check validity with exhaustive
// exploration/IP formulation?

View File

@@ -208,7 +208,6 @@
#include "absl/flags/flag.h"
#include "absl/strings/str_format.h"
#include "ortools/base/logging.h"
#include "ortools/base/types.h"
#include "ortools/graph/ebert_graph.h"
#include "ortools/util/permutation.h"
#include "ortools/util/zvector.h"

View File

@@ -123,14 +123,13 @@
#ifndef OR_TOOLS_GRAPH_MAX_FLOW_H_
#define OR_TOOLS_GRAPH_MAX_FLOW_H_
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/string_view.h"
#include "ortools/base/logging.h"
#include "ortools/base/types.h"
#include "ortools/graph/ebert_graph.h"
#include "ortools/graph/flow_problem.pb.h"
#include "ortools/graph/graph.h"

View File

@@ -168,15 +168,12 @@
#ifndef OR_TOOLS_GRAPH_MIN_COST_FLOW_H_
#define OR_TOOLS_GRAPH_MIN_COST_FLOW_H_
#include <algorithm>
#include <cstdint>
#include <stack>
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "ortools/base/logging.h"
#include "ortools/base/types.h"
#include "ortools/graph/ebert_graph.h"
#include "ortools/graph/graph.h"
#include "ortools/util/stats.h"

View File

@@ -14,15 +14,13 @@
#ifndef OR_TOOLS_GRAPH_MINIMUM_SPANNING_TREE_H_
#define OR_TOOLS_GRAPH_MINIMUM_SPANNING_TREE_H_
#include <queue>
#include <limits>
#include <vector>
#include "absl/types/span.h"
#include "ortools/base/adjustable_priority_queue-inl.h"
#include "ortools/base/adjustable_priority_queue.h"
#include "ortools/base/types.h"
#include "ortools/graph/connected_components.h"
#include "ortools/util/vector_or_function.h"
namespace operations_research {

View File

@@ -52,7 +52,6 @@
#include "absl/container/flat_hash_map.h"
#include "ortools/base/map_util.h"
#include "ortools/base/types.h"
namespace operations_research {

View File

@@ -121,8 +121,6 @@
#ifndef OR_TOOLS_GRAPH_ONE_TREE_LOWER_BOUND_H_
#define OR_TOOLS_GRAPH_ONE_TREE_LOWER_BOUND_H_
#include <math.h>
#include <cmath>
#include <cstdint>
#include <limits>
@@ -131,8 +129,9 @@
#include <vector>
#include "absl/types/span.h"
#include "ortools/base/types.h"
#include "ortools/base/logging.h"
#include "ortools/graph/christofides.h"
#include "ortools/graph/graph.h"
#include "ortools/graph/minimum_spanning_tree.h"
namespace operations_research {

View File

@@ -22,7 +22,11 @@
#include <utility>
#include <vector>
#include "absl/memory/memory.h"
#include "absl/base/log_severity.h"
#include "absl/log/check.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "ortools/base/logging.h"
#include "ortools/util/saturated_arithmetic.h"
namespace operations_research {

View File

@@ -28,21 +28,16 @@
#include <cstdint>
#include <functional>
#include <limits>
#include <memory>
#include <string>
#include <vector>
#include "absl/base/attributes.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "ortools/base/adjustable_priority_queue-inl.h"
#include "ortools/base/adjustable_priority_queue.h"
#include "ortools/base/int_type.h"
#include "ortools/base/logging.h"
#include "ortools/base/macros.h"
#include "ortools/base/strong_vector.h"
#include "ortools/base/types.h"
namespace operations_research {

802
ortools/graph/rooted_tree.h Normal file
View File

@@ -0,0 +1,802 @@
// Copyright 2010-2024 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.
// Find paths and compute path distances between nodes on a rooted tree.
//
// A tree is a connected undirected graph with no cycles. A rooted tree is a
// directed graph derived from a tree, where a node is designated as the root,
// and then all edges are directed towards the root.
//
// This file provides the class RootedTree, which stores a rooted tree on dense
// integer nodes a single vector, and a function RootedTreeFromGraph(), which
// converts the adjacency list of a an undirected tree to a RootedTree.
#ifndef OR_TOOLS_GRAPH_ROOTED_TREE_H_
#define OR_TOOLS_GRAPH_ROOTED_TREE_H_
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "absl/types/span.h"
#include "ortools/base/logging.h"
#include "ortools/base/status_builder.h"
#include "ortools/base/status_macros.h"
namespace operations_research {
// A tree is an undirected graph with no cycles, n nodes, and n-1 undirected
// edges. Consequently, a tree is connected. Given a tree on the nodes [0..n),
// a RootedTree picks any node to be the root, and then converts all edges into
// (directed) arcs pointing at the root. Each node has one outgoing edge, so we
// can store the adjacency list of this directed view of the graph as a single
// vector of integers with length equal to the number of nodes. At the root
// index, we store RootedTree::kNullParent=-1, and at every other index, we
// store the next node towards the root (the parent in the tree).
//
// This class is templated on the NodeType, which must be an integer type, e.g.,
// int or int32_t (signed and unsigned types both work).
//
// The following operations are supported:
// * Path from node to root in O(path length to root)
// * Lowest Common Ancestor (LCA) of two nodes in O(path length between nodes)
// * Depth of all nodes in O(num nodes)
// * Topological sort in O(num nodes)
// * Path between any two nodes in O(path length between nodes)
//
// Users can provide a vector<double> of arc lengths (indexed by source) to get:
// * Distance from node to root in O(path length to root)
// * Distance from all nodes to root in O(num nodes)
// * Distance between any two nodes in O(path length between nodes)
//
// Operations on rooted trees are generally more efficient than on adjacency
// list representations because the entire tree is in one contiguous allocation.
// There is also an asymptotic advantage on path finding problems.
//
// Two methods for finding the LCA are provided. The first requires the depth of
// every node ahead of time. The second requires a workspace of n bools, all
// starting at false. These values are modified and restored to false when the
// LCA computation finishes. In both cases, if the depths/workspace allocation
// is an O(n) precomputation, then the LCA runs in O(path length).
// Non-asymptotically, the depth method requires more precomputation, but the
// LCA is faster and does not require the user to manage mutable state (i.e.,
// may be better for multi-threaded computation).
//
// An operation that is missing is bulk LCA, see
// https://en.wikipedia.org/wiki/Tarjan%27s_off-line_lowest_common_ancestors_algorithm.
template <typename NodeType = int32_t>
class RootedTree {
public:
static constexpr NodeType kNullParent = static_cast<NodeType>(-1);
// Like the constructor but checks that the tree is valid. Uses O(num nodes)
// temporary space with O(log(n)) allocations.
//
// If the input is cyclic, an InvalidArgument error will be returned with
// "cycle" as a substring. Further, if error_cycle is not null, it will be
// cleared and then set to contain the cycle. We will not modify error cycle
// or return an error message containing the string cycle if there is no
// cycle. The cycle output will always begin with its smallest element.
static absl::StatusOr<RootedTree> Create(
NodeType root, std::vector<NodeType> parents,
std::vector<NodeType>* error_cycle = nullptr,
std::vector<NodeType>* topological_order = nullptr);
// Like Create(), but data is not validated (UB on bad input).
explicit RootedTree(NodeType root, std::vector<NodeType> parents)
: root_(root), parents_(std::move(parents)) {}
// The root node of this rooted tree.
NodeType root() const { return root_; }
// The number of nodes in this rooted tree.
NodeType num_nodes() const { return static_cast<NodeType>(parents_.size()); }
// A vector that holds the parent of each non root node, and kNullParent at
// the root.
absl::Span<const NodeType> parents() const { return parents_; }
// Returns the path from `node` to `root()` as a vector of nodes starting with
// `node`.
std::vector<NodeType> PathToRoot(NodeType node) const;
// Returns the path from `root()` to `node` as a vector of nodes starting with
// `node`.
std::vector<NodeType> PathFromRoot(NodeType node) const;
// Returns the sum of the arc lengths of the arcs in the path from `start` to
// `root()`.
//
// `arc_lengths[i]` is the length of the arc from node i to `parents()[i]`.
// `arc_lengths` must have size equal to `num_nodes()` or else we CHECK fail.
// The value of `arc_lengths[root()]` is unused.
double DistanceToRoot(NodeType start,
absl::Span<const double> arc_lengths) const;
// Returns the path from `start` to `root()` as a vector of nodes starting
// with `start`, and the sum of the arc lengths of the arcs in the path.
//
// `arc_lengths[i]` is the length of the arc from node i to `parents()[i]`.
// `arc_lengths` must have size equal to `num_nodes()` or else we CHECK fail.
// The value of `arc_lengths[root()]` is unused.
std::pair<double, std::vector<NodeType>> DistanceAndPathToRoot(
NodeType start, absl::Span<const double> arc_lengths) const;
// Returns the path from `start` to `end` as a vector of nodes starting with
// `start` and ending with `end`.
//
// `lca` is the lowest common ancestor of `start` and `end`. This can be
// computed using LowestCommonAncestorByDepth() or
// LowestCommonAncestorByDepth(), both defined on this class.
//
// Runs in time O(path length).
std::vector<NodeType> Path(NodeType start, NodeType end, NodeType lca) const;
// Returns the sum of the arc lengths of the arcs in the path from `start` to
// `end`.
//
// `lca` is the lowest common ancestor of `start` and `end`. This can be
// computed using LowestCommonAncestorByDepth() or
// LowestCommonAncestorByDepth(), both defined on this class.
//
// `arc_lengths[i]` is the length of the arc from node i to `parents()[i]`.
// `arc_lengths` must have size equal to `num_nodes()` or else we CHECK fail.
// The value of `arc_lengths[root()]` is unused.
//
// Runs in time O(number of edges connecting start to end).
double Distance(NodeType start, NodeType end, NodeType lca,
absl::Span<const double> arc_lengths) const;
// Returns the path from `start` to `end` as a vector of nodes starting with
// `start`, and the sum of the arc lengths of the arcs in the path.
//
// `lca` is the lowest common ancestor of `start` and `end`. This can be
// computed using LowestCommonAncestorByDepth() or
// LowestCommonAncestorByDepth(), both defined on this class.
//
// `arc_lengths[i]` is the length of the arc from node i to `parents()[i]`.
// `arc_lengths` must have size equal to `num_nodes()` or else we CHECK fail.
// The value of `arc_lengths[root()]` is unused.
//
// Runs in time O(number of edges connecting start to end).
std::pair<double, std::vector<NodeType>> DistanceAndPath(
NodeType start, NodeType end, NodeType lca,
absl::Span<const double> arc_lengths) const;
// Given a path of nodes, returns the sum of the length of the arcs connecting
// them.
//
// `path` must be a list of nodes in the tree where
// path[i+1] == parents()[path[i]].
//
// `arc_lengths[i]` is the length of the arc from node i to `parents()[i]`.
// `arc_lengths` must have size equal to `num_nodes()` or else we CHECK fail.
// The value of `arc_lengths[root()]` is unused.
double DistanceOfPath(absl::Span<const NodeType> path,
absl::Span<const double> arc_lengths) const;
// Returns a topological ordering of the nodes where the root is first and
// every other node appears after its parent.
std::vector<NodeType> TopologicalSort() const;
// Returns the distance of every node from `root()`, if the edge leaving node
// i has length costs[i].
//
// `arc_lengths[i]` is the length of the arc from node i to `parents()[i]`.
// `arc_lengths` must have size equal to `num_nodes()` or else we CHECK fail.
// The value of `arc_lengths[root()]` is unused.
//
// If you already have a topological order, prefer
// `AllDistances(absl::Span<const double> costs,
// absl::Span<const int>& topological_order)`.
template <typename T>
std::vector<T> AllDistancesToRoot(absl::Span<const T> arc_lengths) const;
// Returns the distance from every node to root().
//
// `arc_lengths[i]` is the length of the arc from node i to `parents()[i]`.
// `arc_lengths` must have size equal to `num_nodes()` or else we CHECK fail.
// The value of `arc_lengths[root()]` is unused.
//
// `topological_order` must have size equal to `num_nodes()` and start with
// `root()`, or else we CHECK fail. It can be any topological over nodes when
// the orientation of the arcs from rooting the tree is reversed.
template <typename T>
std::vector<T> AllDistancesToRoot(
absl::Span<const T> arc_lengths,
absl::Span<const NodeType> topological_order) const;
// Returns the distance (arcs to move over) from every node to the root.
//
// If you already have a topological order, prefer
// AllDepths(absl::Span<const NodeType>).
std::vector<NodeType> AllDepths() const {
return AllDepths(TopologicalSort());
}
// Returns the distance (arcs to move over) from every node to the root.
//
// `topological_order` must have size equal to `num_nodes()` and start with
// `root()`, or else we CHECK fail. It can be any topological over nodes when
// the orientation of the arcs from rooting the tree is reversed.
std::vector<NodeType> AllDepths(
absl::Span<const NodeType> topological_order) const;
// Returns the lowest common ancestor of n1 and n2.
//
// `depths` must have size equal to `num_nodes()`, or else we CHECK fail.
// Values must be the distance of each node to the root in arcs (see
// AllDepths()).
NodeType LowestCommonAncestorByDepth(NodeType n1, NodeType n2,
absl::Span<const NodeType> depths) const;
// Returns the lowest common ancestor of n1 and n2.
//
// `visited_workspace` must be a vector with num_nodes() size, or else we
// CHECK fail. All values of `visited_workspace` should be false. It will be
// modified and then restored to its starting state.
NodeType LowestCommonAncestorBySearch(
NodeType n1, NodeType n2, std::vector<bool>& visited_workspace) const;
// Modifies the tree in place to change the root. Runs in
// O(path length from root() to new_root).
void Evert(NodeType new_root);
private:
static_assert(std::is_integral_v<NodeType>,
"NodeType must be an integral type.");
static_assert(sizeof(NodeType) <= sizeof(std::size_t),
"NodeType cannot be larger than size_t, because NodeType is "
"used to index into std::vector.");
// Returns the number of nodes appended.
NodeType AppendToPath(NodeType start, NodeType end,
std::vector<NodeType>& path) const;
// Returns the number of nodes appended.
NodeType ReverseAppendToPath(NodeType start, NodeType end,
std::vector<NodeType>& path) const;
// Like AllDistancestoRoot(), but the input arc_lengths is mutated to hold
// the output, instead of just returning the output as a new vector.
template <typename T>
void AllDistancesToRootInPlace(
absl::Span<const NodeType> topological_order,
absl::Span<T> arc_lengths_in_distances_out) const;
// Returns the cost of the path from start to end.
//
// end must be either equal to an or ancestor of start in the tree (otherwise
// DCHECK/UB).
//
// `arc_lengths[i]` is the length of the arc from node i to `parents()[i]`.
// `arc_lengths` must have size equal to `num_nodes()` or else we CHECK fail.
// The value of `arc_lengths[root()]` is unused.
double DistanceOfUpwardPath(NodeType start, NodeType end,
absl::Span<const double> arc_lengths) const;
int root_;
std::vector<NodeType> parents_; // kNullParent=-1 if root
};
////////////////////////////////////////////////////////////////////////////////
// Graph API
////////////////////////////////////////////////////////////////////////////////
// Converts an adjacency list representation of an undirected tree into a rooted
// tree.
//
// Graph must meet the API defined in ortools/graph/graph.h, e.g., StaticGraph
// or ListGraph. Note that these are directed graph APIs, so they must have both
// forward and backward arcs for each edge in the tree.
//
// Graph must be a tree when viewed as an undirected graph.
//
// If topological_order is not null, it is set to a vector with one entry for
// each node giving a topological ordering over the nodes of the graph, with the
// root first.
//
// If depths is not null, it is set to a vector with one entry for each node,
// giving the depth in the tree of that node (the root has depth zero).
template <typename Graph>
absl::StatusOr<RootedTree<typename Graph::NodeType>> RootedTreeFromGraph(
typename Graph::NodeType root, const Graph& graph,
std::vector<typename Graph::NodeType>* topological_order = nullptr,
std::vector<typename Graph::NodeType>* depths = nullptr);
////////////////////////////////////////////////////////////////////////////////
// Template implementations
////////////////////////////////////////////////////////////////////////////////
namespace internal {
template <typename NodeType>
bool IsValidParent(const NodeType node, const NodeType num_tree_nodes) {
return node == RootedTree<NodeType>::kNullParent ||
(node >= NodeType{0} && node < num_tree_nodes);
}
template <typename NodeType>
absl::Status IsValidNode(const NodeType node, const NodeType num_tree_nodes) {
if (node < NodeType{0} || node >= num_tree_nodes) {
return util::InvalidArgumentErrorBuilder()
<< "nodes must be in [0.." << num_tree_nodes
<< "), found bad node: " << node;
}
return absl::OkStatus();
}
template <typename NodeType>
std::vector<NodeType> ExtractCycle(absl::Span<const NodeType> parents,
const NodeType node_in_cycle) {
std::vector<NodeType> cycle;
cycle.push_back(node_in_cycle);
for (NodeType i = parents[node_in_cycle]; i != node_in_cycle;
i = parents[i]) {
CHECK_NE(i, RootedTree<NodeType>::kNullParent)
<< "node_in_cycle: " << node_in_cycle
<< " not in cycle, reached the root";
cycle.push_back(i);
CHECK_LE(cycle.size(), parents.size())
<< "node_in_cycle: " << node_in_cycle
<< " not in cycle, just (transitively) leads to a cycle";
}
absl::c_rotate(cycle, absl::c_min_element(cycle));
cycle.push_back(cycle[0]);
return cycle;
}
template <typename NodeType>
std::string CycleErrorMessage(absl::Span<const NodeType> cycle) {
CHECK_GT(cycle.size(), 0);
const NodeType start = cycle[0];
std::string cycle_string;
if (cycle.size() > 10) {
cycle_string = absl::StrCat(
absl::StrJoin(absl::MakeConstSpan(cycle).subspan(0, 8), ", "),
", ..., ", start);
} else {
cycle_string = absl::StrJoin(cycle, ", ");
}
return absl::StrCat("found cycle of size: ", cycle.size(),
" with nodes: ", cycle_string);
}
// Every element of parents must be in {kNullParent} union [0..parents.size()),
// otherwise UB.
template <typename NodeType>
std::vector<NodeType> CheckForCycle(absl::Span<const NodeType> parents,
std::vector<NodeType>* topological_order) {
const NodeType n = static_cast<NodeType>(parents.size());
if (topological_order != nullptr) {
topological_order->clear();
topological_order->reserve(n);
}
std::vector<bool> visited(n);
std::vector<NodeType> dfs_stack;
for (NodeType i = 0; i < n; ++i) {
if (visited[i]) {
continue;
}
NodeType next = i;
while (next != RootedTree<NodeType>::kNullParent && !visited[next]) {
dfs_stack.push_back(next);
if (dfs_stack.size() > n) {
if (topological_order != nullptr) {
topological_order->clear();
}
return ExtractCycle(parents, next);
}
next = parents[next];
DCHECK(IsValidParent(next, n)) << "next: " << next << ", n: " << n;
}
absl::c_reverse(dfs_stack);
for (const NodeType j : dfs_stack) {
visited[j] = true;
if (topological_order != nullptr) {
topological_order->push_back(j);
}
}
dfs_stack.clear();
}
return {};
}
} // namespace internal
template <typename NodeType>
NodeType RootedTree<NodeType>::AppendToPath(const NodeType start,
const NodeType end,
std::vector<NodeType>& path) const {
NodeType num_new = 0;
for (NodeType node = start; node != end; node = parents_[node]) {
DCHECK_NE(node, kNullParent);
path.push_back(node);
num_new++;
}
path.push_back(end);
return num_new + 1;
}
template <typename NodeType>
NodeType RootedTree<NodeType>::ReverseAppendToPath(
NodeType start, NodeType end, std::vector<NodeType>& path) const {
NodeType result = AppendToPath(start, end, path);
std::reverse(path.end() - result, path.end());
return result;
}
template <typename NodeType>
double RootedTree<NodeType>::DistanceOfUpwardPath(
const NodeType start, const NodeType end,
absl::Span<const double> arc_lengths) const {
CHECK_EQ(num_nodes(), arc_lengths.size());
double distance = 0.0;
for (NodeType next = start; next != end; next = parents_[next]) {
DCHECK_NE(next, root_);
distance += arc_lengths[next];
}
return distance;
}
template <typename NodeType>
absl::StatusOr<RootedTree<NodeType>> RootedTree<NodeType>::Create(
const NodeType root, std::vector<NodeType> parents,
std::vector<NodeType>* error_cycle,
std::vector<NodeType>* topological_order) {
const NodeType num_nodes = static_cast<NodeType>(parents.size());
RETURN_IF_ERROR(internal::IsValidNode(root, num_nodes)) << "invalid root";
if (parents[root] != kNullParent) {
return util::InvalidArgumentErrorBuilder()
<< "root should have no parent (-1), but found parent of: "
<< parents[root];
}
for (NodeType i = 0; i < num_nodes; ++i) {
if (i == root) {
continue;
}
RETURN_IF_ERROR(internal::IsValidNode(parents[i], num_nodes))
<< "invalid value for parent of node: " << i;
}
std::vector<NodeType> cycle =
internal::CheckForCycle(absl::MakeConstSpan(parents), topological_order);
if (!cycle.empty()) {
std::string error_message =
internal::CycleErrorMessage(absl::MakeConstSpan(cycle));
if (error_cycle != nullptr) {
*error_cycle = std::move(cycle);
}
return absl::InvalidArgumentError(std::move(error_message));
}
return RootedTree(root, std::move(parents));
}
template <typename NodeType>
std::vector<NodeType> RootedTree<NodeType>::PathToRoot(
const NodeType node) const {
std::vector<NodeType> path;
for (NodeType next = node; next != root_; next = parents_[next]) {
path.push_back(next);
}
path.push_back(root_);
return path;
}
template <typename NodeType>
std::vector<NodeType> RootedTree<NodeType>::PathFromRoot(
const NodeType node) const {
std::vector<NodeType> result = PathToRoot(node);
absl::c_reverse(result);
return result;
}
template <typename NodeType>
std::vector<NodeType> RootedTree<NodeType>::TopologicalSort() const {
std::vector<NodeType> result;
const std::vector<NodeType> cycle =
internal::CheckForCycle(absl::MakeConstSpan(parents_), &result);
CHECK(cycle.empty()) << internal::CycleErrorMessage(
absl::MakeConstSpan(cycle));
return result;
}
template <typename NodeType>
double RootedTree<NodeType>::DistanceToRoot(
const NodeType start, absl::Span<const double> arc_lengths) const {
return DistanceOfUpwardPath(start, root_, arc_lengths);
}
template <typename NodeType>
std::pair<double, std::vector<NodeType>>
RootedTree<NodeType>::DistanceAndPathToRoot(
const NodeType start, absl::Span<const double> arc_lengths) const {
CHECK_EQ(num_nodes(), arc_lengths.size());
double distance = 0.0;
std::vector<NodeType> path;
for (NodeType next = start; next != root_; next = parents_[next]) {
path.push_back(next);
distance += arc_lengths[next];
}
path.push_back(root_);
return {distance, path};
}
template <typename NodeType>
std::vector<NodeType> RootedTree<NodeType>::Path(const NodeType start,
const NodeType end,
const NodeType lca) const {
std::vector<NodeType> result;
if (start == end) {
result.push_back(start);
return result;
}
if (start == lca) {
ReverseAppendToPath(end, lca, result);
return result;
}
if (end == lca) {
AppendToPath(start, lca, result);
return result;
}
AppendToPath(start, lca, result);
result.pop_back(); // Don't include the LCA twice
ReverseAppendToPath(end, lca, result);
return result;
}
template <typename NodeType>
double RootedTree<NodeType>::Distance(
const NodeType start, const NodeType end, const NodeType lca,
absl::Span<const double> arc_lengths) const {
return DistanceOfUpwardPath(start, lca, arc_lengths) +
DistanceOfUpwardPath(end, lca, arc_lengths);
}
template <typename NodeType>
std::pair<double, std::vector<NodeType>> RootedTree<NodeType>::DistanceAndPath(
const NodeType start, const NodeType end, const NodeType lca,
absl::Span<const double> arc_lengths) const {
std::vector<NodeType> path = Path(start, end, lca);
const double dist = DistanceOfPath(path, arc_lengths);
return {dist, std::move(path)};
}
template <typename NodeType>
double RootedTree<NodeType>::DistanceOfPath(
absl::Span<const NodeType> path,
absl::Span<const double> arc_lengths) const {
CHECK_EQ(num_nodes(), arc_lengths.size());
double distance = 0.0;
for (int i = 0; i + 1 < path.size(); ++i) {
if (parents_[path[i]] != path[i + 1]) {
distance += arc_lengths[path[i]];
} else if (parents_[path[i + 1]] == path[i]) {
distance += arc_lengths[path[i + 1]];
} else {
LOG(FATAL) << "bad edge in path from " << path[i] << " to "
<< path[i + 1];
}
}
return distance;
}
template <typename NodeType>
NodeType RootedTree<NodeType>::LowestCommonAncestorByDepth(
const NodeType n1, const NodeType n2,
absl::Span<const NodeType> depths) const {
CHECK_EQ(num_nodes(), depths.size());
const NodeType n = num_nodes();
CHECK_OK(internal::IsValidNode(n1, n));
CHECK_OK(internal::IsValidNode(n2, n));
CHECK_EQ(depths.size(), n);
if (n1 == root_ || n2 == root_) {
return root_;
}
if (n1 == n2) {
return n1;
}
NodeType next1 = n1;
NodeType next2 = n2;
while (depths[next1] > depths[next2]) {
next1 = parents_[next1];
}
while (depths[next2] > depths[next1]) {
next2 = parents_[next2];
}
while (next1 != next2) {
next1 = parents_[next1];
next2 = parents_[next2];
}
return next1;
}
template <typename NodeType>
NodeType RootedTree<NodeType>::LowestCommonAncestorBySearch(
const NodeType n1, const NodeType n2,
std::vector<bool>& visited_workspace) const {
const NodeType n = num_nodes();
CHECK_OK(internal::IsValidNode(n1, n));
CHECK_OK(internal::IsValidNode(n2, n));
CHECK_EQ(visited_workspace.size(), n);
if (n1 == root_ || n2 == root_) {
return root_;
}
if (n1 == n2) {
return n1;
}
NodeType next1 = n1;
NodeType next2 = n2;
visited_workspace[n1] = true;
visited_workspace[n2] = true;
NodeType lca = kNullParent;
NodeType lca_distance =
1; // used only for cleanup purposes, can over estimate
while (true) {
lca_distance++;
if (next1 != root_) {
next1 = parents_[next1];
if (visited_workspace[next1]) {
lca = next1;
break;
}
}
if (next2 != root_) {
visited_workspace[next1] = true;
next2 = parents_[next2];
if (visited_workspace[next2]) {
lca = next2;
break;
}
visited_workspace[next2] = true;
}
}
CHECK_OK(internal::IsValidNode(lca, n));
auto cleanup = [this, lca_distance, &visited_workspace](NodeType next) {
for (NodeType i = 0; i < lca_distance && next != kNullParent; ++i) {
visited_workspace[next] = false;
next = parents_[next];
}
};
cleanup(n1);
cleanup(n2);
return lca;
}
template <typename NodeType>
void RootedTree<NodeType>::Evert(const NodeType new_root) {
NodeType previous_node = kNullParent;
for (NodeType node = new_root; node != kNullParent;) {
NodeType next_node = parents_[node];
parents_[node] = previous_node;
previous_node = node;
node = next_node;
}
root_ = new_root;
}
template <typename NodeType>
template <typename T>
void RootedTree<NodeType>::AllDistancesToRootInPlace(
absl::Span<const NodeType> topological_order,
absl::Span<T> arc_lengths_in_distances_out) const {
CHECK_EQ(num_nodes(), arc_lengths_in_distances_out.size());
CHECK_EQ(num_nodes(), topological_order.size());
if (!topological_order.empty()) {
CHECK_EQ(topological_order[0], root_);
}
for (const NodeType node : topological_order) {
if (parents_[node] == kNullParent) {
arc_lengths_in_distances_out[node] = 0;
} else {
arc_lengths_in_distances_out[node] +=
arc_lengths_in_distances_out[parents_[node]];
}
}
}
template <typename NodeType>
std::vector<NodeType> RootedTree<NodeType>::AllDepths(
absl::Span<const NodeType> topological_order) const {
std::vector<NodeType> arc_length_in_distance_out(num_nodes(), 1);
AllDistancesToRootInPlace(topological_order,
absl::MakeSpan(arc_length_in_distance_out));
return arc_length_in_distance_out;
}
template <typename NodeType>
template <typename T>
std::vector<T> RootedTree<NodeType>::AllDistancesToRoot(
absl::Span<const T> arc_lengths) const {
return AllDistancesToRoot(arc_lengths, TopologicalSort());
}
template <typename NodeType>
template <typename T>
std::vector<T> RootedTree<NodeType>::AllDistancesToRoot(
absl::Span<const T> arc_lengths,
absl::Span<const NodeType> topological_order) const {
std::vector<T> distances(arc_lengths.begin(), arc_lengths.end());
AllDistancesToRootInPlace(topological_order, absl::MakeSpan(distances));
return distances;
}
template <typename Graph>
absl::StatusOr<RootedTree<typename Graph::NodeIndex>> RootedTreeFromGraph(
const typename Graph::NodeIndex root, const Graph& graph,
std::vector<typename Graph::NodeIndex>* const topological_order,
std::vector<typename Graph::NodeIndex>* const depths) {
using NodeIndex = typename Graph::NodeIndex;
const NodeIndex num_nodes = graph.num_nodes();
RETURN_IF_ERROR(internal::IsValidNode(root, num_nodes))
<< "invalid root node";
if (topological_order != nullptr) {
topological_order->clear();
topological_order->reserve(num_nodes);
topological_order->push_back(root);
}
if (depths != nullptr) {
depths->clear();
depths->resize(num_nodes, 0);
}
std::vector<NodeIndex> tree(num_nodes, RootedTree<NodeIndex>::kNullParent);
auto visited = [&tree, root](const NodeIndex node) {
if (node == root) {
return true;
}
return tree[node] != RootedTree<NodeIndex>::kNullParent;
};
std::vector<NodeIndex> must_search_children = {root};
while (!must_search_children.empty()) {
NodeIndex next = must_search_children.back();
must_search_children.pop_back();
for (const NodeIndex neighbor : graph[next]) {
if (visited(neighbor)) {
if (tree[next] == neighbor) {
continue;
} else {
// NOTE: this will also catch nodes with self loops.
return util::InvalidArgumentErrorBuilder()
<< "graph has cycle containing arc from " << next << " to "
<< neighbor;
}
}
tree[neighbor] = next;
if (topological_order != nullptr) {
topological_order->push_back(neighbor);
}
if (depths != nullptr) {
(*depths)[neighbor] = (*depths)[next] + 1;
}
must_search_children.push_back(neighbor);
}
}
for (NodeIndex i = 0; i < num_nodes; ++i) {
if (!visited(i)) {
return util::InvalidArgumentErrorBuilder()
<< "graph is not connected, no path to " << i;
}
}
return RootedTree<NodeIndex>(root, tree);
}
} // namespace operations_research
#endif // OR_TOOLS_GRAPH_ROOTED_TREE_H_

View File

@@ -0,0 +1,653 @@
// Copyright 2010-2024 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.
#include "ortools/graph/rooted_tree.h"
#include <algorithm>
#include <cstdint>
#include <utility>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/log/check.h"
#include "absl/random/random.h"
#include "absl/status/status.h"
#include "benchmark/benchmark.h"
#include "gtest/gtest.h"
#include "ortools/base/gmock.h"
#include "ortools/graph/graph.h"
namespace operations_research {
namespace {
using ::testing::AnyOf;
using ::testing::DoubleEq;
using ::testing::Each;
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
using ::testing::HasSubstr;
using ::testing::IsFalse;
using ::testing::Pair;
using ::testing::SizeIs;
using ::testing::status::StatusIs;
////////////////////////////////////////////////////////////////////////////////
// RootedTree Tests
////////////////////////////////////////////////////////////////////////////////
template <typename T>
class RootedTreeTest : public testing::Test {
public:
static constexpr T kNullParent = RootedTree<T>::kNullParent;
};
TYPED_TEST_SUITE_P(RootedTreeTest);
TYPED_TEST_P(RootedTreeTest, CreateFailsRootOutOfBoundsInvalidArgument) {
using Node = TypeParam;
const Node root = 5;
std::vector<Node> parents = {0, this->kNullParent};
EXPECT_THAT(RootedTree<Node>::Create(root, parents),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("root")));
}
TYPED_TEST_P(RootedTreeTest, CreateFailsRootHasParentInvalidArgument) {
using Node = TypeParam;
const Node root = 0;
std::vector<Node> parents = {1, 0};
EXPECT_THAT(RootedTree<Node>::Create(root, parents),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("root")));
}
TYPED_TEST_P(RootedTreeTest, CreateFailsExtraRootInvalidArgument) {
using Node = TypeParam;
const Node root = 1;
std::vector<Node> parents = {this->kNullParent, this->kNullParent};
EXPECT_THAT(
RootedTree<Node>::Create(root, parents),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("parent")));
}
TYPED_TEST_P(RootedTreeTest, CreateFailsBadParentInvalidArgument) {
using Node = TypeParam;
const Node root = 1;
std::vector<Node> parents = {3, this->kNullParent};
EXPECT_THAT(
RootedTree<Node>::Create(root, parents),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("parent")));
}
TYPED_TEST_P(RootedTreeTest, CreateFailsIsolatedCycleInvalidArgument) {
using Node = TypeParam;
const Node root = 3;
std::vector<Node> parents = {1, 2, 0, this->kNullParent, 3};
EXPECT_THAT(RootedTree<Node>::Create(root, parents),
StatusIs(absl::StatusCode::kInvalidArgument,
AllOf(HasSubstr("cycle"), HasSubstr("0, 1, 2"))));
std::vector<Node> cycle;
EXPECT_THAT(RootedTree<Node>::Create(root, parents, &cycle),
StatusIs(absl::StatusCode::kInvalidArgument,
AllOf(HasSubstr("cycle"), HasSubstr("0, 1, 2"))));
EXPECT_THAT(cycle, ElementsAre(0, 1, 2, 0));
}
TYPED_TEST_P(RootedTreeTest, CreateFailsPathLeadsToCycleInvalidArgument) {
using Node = TypeParam;
const Node root = 3;
std::vector<Node> parents = {1, 2, 0, this->kNullParent, 0};
EXPECT_THAT(RootedTree<Node>::Create(root, parents),
StatusIs(absl::StatusCode::kInvalidArgument,
AllOf(HasSubstr("cycle"), HasSubstr("0, 1, 2"))));
std::vector<Node> cycle;
EXPECT_THAT(RootedTree<Node>::Create(root, parents, &cycle),
StatusIs(absl::StatusCode::kInvalidArgument,
AllOf(HasSubstr("cycle"), HasSubstr("0, 1, 2"))));
EXPECT_THAT(cycle, ElementsAre(0, 1, 2, 0));
}
TYPED_TEST_P(RootedTreeTest, CreatePathFailsLongCycleErrorIsTruncated) {
using Node = TypeParam;
const Node root = 50;
std::vector<Node> parents(51);
parents[root] = this->kNullParent;
for (Node i = 0; i < Node{50}; ++i) {
parents[i] = (i + 1) % Node{50};
}
EXPECT_THAT(RootedTree<Node>::Create(root, parents),
StatusIs(absl::StatusCode::kInvalidArgument,
AllOf(HasSubstr("cycle"),
HasSubstr("0, 1, 2, 3, 4, 5, 6, 7, ..., 0"))));
std::vector<Node> cycle;
EXPECT_THAT(RootedTree<Node>::Create(root, parents, &cycle),
StatusIs(absl::StatusCode::kInvalidArgument,
AllOf(HasSubstr("cycle"),
HasSubstr("0, 1, 2, 3, 4, 5, 6, 7, ..., 0"))));
std::vector<Node> expected_cycle(50);
absl::c_iota(expected_cycle, 0);
expected_cycle.push_back(0);
EXPECT_THAT(cycle, ElementsAreArray(expected_cycle));
}
TYPED_TEST_P(RootedTreeTest, PathToRoot) {
using Node = TypeParam;
// 1
// / |
// 0 3
// |
// 2
const Node root = 1;
std::vector<Node> parents = {1, this->kNullParent, 3, 1};
ASSERT_OK_AND_ASSIGN(const auto tree,
RootedTree<Node>::Create(root, parents));
EXPECT_THAT(tree.PathToRoot(0), ElementsAre(0, 1));
EXPECT_THAT(tree.PathToRoot(1), ElementsAre(1));
EXPECT_THAT(tree.PathToRoot(2), ElementsAre(2, 3, 1));
EXPECT_THAT(tree.PathToRoot(3), ElementsAre(3, 1));
}
TYPED_TEST_P(RootedTreeTest, DistanceToRoot) {
using Node = TypeParam;
// 1
// / |
// 0 3
// |
// 2
const int root = 1;
std::vector<Node> parents = {1, this->kNullParent, 3, 1};
std::vector<double> arc_lengths = {1, 0, 10, 100};
ASSERT_OK_AND_ASSIGN(const auto tree,
RootedTree<Node>::Create(root, parents));
EXPECT_DOUBLE_EQ(tree.DistanceToRoot(0, arc_lengths), 1);
EXPECT_DOUBLE_EQ(tree.DistanceToRoot(1, arc_lengths), 0);
EXPECT_DOUBLE_EQ(tree.DistanceToRoot(2, arc_lengths), 110);
EXPECT_DOUBLE_EQ(tree.DistanceToRoot(3, arc_lengths), 100);
}
TYPED_TEST_P(RootedTreeTest, DistanceAndPathToRoot) {
using Node = TypeParam;
// 1
// / |
// 0 3
// |
// 2
const Node root = 1;
std::vector<Node> parents = {1, this->kNullParent, 3, 1};
std::vector<double> arc_lengths = {1, 0, 10, 100};
ASSERT_OK_AND_ASSIGN(const auto tree,
RootedTree<Node>::Create(root, parents));
EXPECT_THAT(tree.DistanceAndPathToRoot(0, arc_lengths),
Pair(DoubleEq(1.0), ElementsAre(0, 1)));
EXPECT_THAT(tree.DistanceAndPathToRoot(1, arc_lengths),
Pair(DoubleEq(0.0), ElementsAre(1)));
EXPECT_THAT(tree.DistanceAndPathToRoot(2, arc_lengths),
Pair(DoubleEq(110.0), ElementsAre(2, 3, 1)));
EXPECT_THAT(tree.DistanceAndPathToRoot(3, arc_lengths),
Pair(DoubleEq(100.0), ElementsAre(3, 1)));
}
TYPED_TEST_P(RootedTreeTest, TopologicalSort) {
using Node = TypeParam;
// 1
// / |
// 0 3
// |
// 2
const Node root = 1;
std::vector<Node> parents = {1, this->kNullParent, 3, 1};
ASSERT_OK_AND_ASSIGN(const auto tree,
RootedTree<Node>::Create(root, parents));
EXPECT_THAT(tree.TopologicalSort(),
AnyOf(ElementsAre(1, 0, 3, 2), ElementsAre(1, 3, 2, 0),
ElementsAre(1, 3, 0, 2)));
}
TYPED_TEST_P(RootedTreeTest, AllDistancesToRoot) {
using Node = TypeParam;
// 1
// / |
// 0 3
// |
// 2
const int root = 1;
std::vector<Node> parents = {1, this->kNullParent, 3, 1};
const std::vector<double> arc_lengths = {1, 0, 10, 100};
ASSERT_OK_AND_ASSIGN(const auto tree,
RootedTree<Node>::Create(root, parents));
EXPECT_THAT(tree.AllDistancesToRoot<double>(arc_lengths),
ElementsAre(1.0, 0.0, 110.0, 100.0));
}
TYPED_TEST_P(RootedTreeTest, AllDepths) {
using Node = TypeParam;
// 1
// / |
// 0 3
// |
// 2
const Node root = 1;
std::vector<Node> parents = {1, this->kNullParent, 3, 1};
ASSERT_OK_AND_ASSIGN(const auto tree,
RootedTree<Node>::Create(root, parents));
EXPECT_THAT(tree.AllDepths(), ElementsAre(1, 0, 2, 1));
}
TYPED_TEST_P(RootedTreeTest, LCAByDepth) {
using Node = TypeParam;
// 4
// /
// 1
// / |
// 0 3
// |
// 2
const int root = 4;
std::vector<Node> parents = {1, 4, 3, 1, this->kNullParent};
ASSERT_OK_AND_ASSIGN(const auto tree,
RootedTree<Node>::Create(root, parents));
const std::vector<Node> depths = {2, 1, 3, 2, 0};
ASSERT_THAT(tree.AllDepths(), ElementsAreArray(depths));
EXPECT_EQ(tree.LowestCommonAncestorByDepth(0, 0, depths), 0);
EXPECT_EQ(tree.LowestCommonAncestorByDepth(0, 1, depths), 1);
EXPECT_EQ(tree.LowestCommonAncestorByDepth(0, 2, depths), 1);
EXPECT_EQ(tree.LowestCommonAncestorByDepth(0, 3, depths), 1);
EXPECT_EQ(tree.LowestCommonAncestorByDepth(0, 4, depths), 4);
EXPECT_EQ(tree.LowestCommonAncestorByDepth(1, 0, depths), 1);
EXPECT_EQ(tree.LowestCommonAncestorByDepth(1, 1, depths), 1);
EXPECT_EQ(tree.LowestCommonAncestorByDepth(1, 2, depths), 1);
EXPECT_EQ(tree.LowestCommonAncestorByDepth(1, 3, depths), 1);
EXPECT_EQ(tree.LowestCommonAncestorByDepth(1, 4, depths), 4);
EXPECT_EQ(tree.LowestCommonAncestorByDepth(2, 0, depths), 1);
EXPECT_EQ(tree.LowestCommonAncestorByDepth(2, 1, depths), 1);
EXPECT_EQ(tree.LowestCommonAncestorByDepth(2, 2, depths), 2);
EXPECT_EQ(tree.LowestCommonAncestorByDepth(2, 3, depths), 3);
EXPECT_EQ(tree.LowestCommonAncestorByDepth(2, 4, depths), 4);
EXPECT_EQ(tree.LowestCommonAncestorByDepth(3, 0, depths), 1);
EXPECT_EQ(tree.LowestCommonAncestorByDepth(3, 1, depths), 1);
EXPECT_EQ(tree.LowestCommonAncestorByDepth(3, 2, depths), 3);
EXPECT_EQ(tree.LowestCommonAncestorByDepth(3, 3, depths), 3);
EXPECT_EQ(tree.LowestCommonAncestorByDepth(3, 4, depths), 4);
EXPECT_EQ(tree.LowestCommonAncestorByDepth(4, 0, depths), 4);
EXPECT_EQ(tree.LowestCommonAncestorByDepth(4, 1, depths), 4);
EXPECT_EQ(tree.LowestCommonAncestorByDepth(4, 2, depths), 4);
EXPECT_EQ(tree.LowestCommonAncestorByDepth(4, 3, depths), 4);
EXPECT_EQ(tree.LowestCommonAncestorByDepth(4, 4, depths), 4);
}
TYPED_TEST_P(RootedTreeTest, LCAByBySearch) {
using Node = TypeParam;
// 4
// /
// 1
// / |
// 0 3
// |
// 2
const Node root = 4;
std::vector<Node> parents = {1, 4, 3, 1, this->kNullParent};
ASSERT_OK_AND_ASSIGN(const auto tree,
RootedTree<Node>::Create(root, parents));
std::vector<bool> ws(5, false);
EXPECT_EQ(tree.LowestCommonAncestorBySearch(0, 0, ws), 0);
ASSERT_THAT(ws, AllOf(SizeIs(5), Each(IsFalse())));
EXPECT_EQ(tree.LowestCommonAncestorBySearch(0, 1, ws), 1);
ASSERT_THAT(ws, AllOf(SizeIs(5), Each(IsFalse())));
EXPECT_EQ(tree.LowestCommonAncestorBySearch(0, 2, ws), 1);
ASSERT_THAT(ws, AllOf(SizeIs(5), Each(IsFalse())));
EXPECT_EQ(tree.LowestCommonAncestorBySearch(0, 3, ws), 1);
ASSERT_THAT(ws, AllOf(SizeIs(5), Each(IsFalse())));
EXPECT_EQ(tree.LowestCommonAncestorBySearch(0, 4, ws), 4);
ASSERT_THAT(ws, AllOf(SizeIs(5), Each(IsFalse())));
EXPECT_EQ(tree.LowestCommonAncestorBySearch(1, 0, ws), 1);
ASSERT_THAT(ws, AllOf(SizeIs(5), Each(IsFalse())));
EXPECT_EQ(tree.LowestCommonAncestorBySearch(1, 1, ws), 1);
ASSERT_THAT(ws, AllOf(SizeIs(5), Each(IsFalse())));
EXPECT_EQ(tree.LowestCommonAncestorBySearch(1, 2, ws), 1);
ASSERT_THAT(ws, AllOf(SizeIs(5), Each(IsFalse())));
EXPECT_EQ(tree.LowestCommonAncestorBySearch(1, 3, ws), 1);
ASSERT_THAT(ws, AllOf(SizeIs(5), Each(IsFalse())));
EXPECT_EQ(tree.LowestCommonAncestorBySearch(1, 4, ws), 4);
ASSERT_THAT(ws, AllOf(SizeIs(5), Each(IsFalse())));
EXPECT_EQ(tree.LowestCommonAncestorBySearch(2, 0, ws), 1);
ASSERT_THAT(ws, AllOf(SizeIs(5), Each(IsFalse())));
EXPECT_EQ(tree.LowestCommonAncestorBySearch(2, 1, ws), 1);
ASSERT_THAT(ws, AllOf(SizeIs(5), Each(IsFalse())));
EXPECT_EQ(tree.LowestCommonAncestorBySearch(2, 2, ws), 2);
ASSERT_THAT(ws, AllOf(SizeIs(5), Each(IsFalse())));
EXPECT_EQ(tree.LowestCommonAncestorBySearch(2, 3, ws), 3);
ASSERT_THAT(ws, AllOf(SizeIs(5), Each(IsFalse())));
EXPECT_EQ(tree.LowestCommonAncestorBySearch(2, 4, ws), 4);
ASSERT_THAT(ws, AllOf(SizeIs(5), Each(IsFalse())));
EXPECT_EQ(tree.LowestCommonAncestorBySearch(3, 0, ws), 1);
ASSERT_THAT(ws, AllOf(SizeIs(5), Each(IsFalse())));
EXPECT_EQ(tree.LowestCommonAncestorBySearch(3, 1, ws), 1);
ASSERT_THAT(ws, AllOf(SizeIs(5), Each(IsFalse())));
EXPECT_EQ(tree.LowestCommonAncestorBySearch(3, 2, ws), 3);
ASSERT_THAT(ws, AllOf(SizeIs(5), Each(IsFalse())));
EXPECT_EQ(tree.LowestCommonAncestorBySearch(3, 3, ws), 3);
ASSERT_THAT(ws, AllOf(SizeIs(5), Each(IsFalse())));
EXPECT_EQ(tree.LowestCommonAncestorBySearch(3, 4, ws), 4);
ASSERT_THAT(ws, AllOf(SizeIs(5), Each(IsFalse())));
EXPECT_EQ(tree.LowestCommonAncestorBySearch(4, 0, ws), 4);
ASSERT_THAT(ws, AllOf(SizeIs(5), Each(IsFalse())));
EXPECT_EQ(tree.LowestCommonAncestorBySearch(4, 1, ws), 4);
ASSERT_THAT(ws, AllOf(SizeIs(5), Each(IsFalse())));
EXPECT_EQ(tree.LowestCommonAncestorBySearch(4, 2, ws), 4);
ASSERT_THAT(ws, AllOf(SizeIs(5), Each(IsFalse())));
EXPECT_EQ(tree.LowestCommonAncestorBySearch(4, 3, ws), 4);
ASSERT_THAT(ws, AllOf(SizeIs(5), Each(IsFalse())));
EXPECT_EQ(tree.LowestCommonAncestorBySearch(4, 4, ws), 4);
ASSERT_THAT(ws, AllOf(SizeIs(5), Each(IsFalse())));
}
TYPED_TEST_P(RootedTreeTest, Path) {
using Node = TypeParam;
// 4
// /
// 1
// / |
// 0 3
// |
// 2
const Node root = 4;
std::vector<Node> parents = {1, 4, 3, 1, this->kNullParent};
ASSERT_OK_AND_ASSIGN(const auto tree,
RootedTree<Node>::Create(root, parents));
std::vector<Node> depths = {2, 1, 3, 2, 0};
ASSERT_THAT(tree.AllDepths(), ElementsAreArray(depths));
auto path = [&tree, &depths](Node start, Node end) {
const Node lca = tree.LowestCommonAncestorByDepth(start, end, depths);
return tree.Path(start, end, lca);
};
EXPECT_THAT(path(0, 0), ElementsAre(0));
EXPECT_THAT(path(0, 1), ElementsAre(0, 1));
EXPECT_THAT(path(0, 2), ElementsAre(0, 1, 3, 2));
EXPECT_THAT(path(0, 3), ElementsAre(0, 1, 3));
EXPECT_THAT(path(0, 4), ElementsAre(0, 1, 4));
EXPECT_THAT(path(1, 0), ElementsAre(1, 0));
EXPECT_THAT(path(1, 1), ElementsAre(1));
EXPECT_THAT(path(1, 2), ElementsAre(1, 3, 2));
EXPECT_THAT(path(1, 3), ElementsAre(1, 3));
EXPECT_THAT(path(1, 4), ElementsAre(1, 4));
EXPECT_THAT(path(2, 0), ElementsAre(2, 3, 1, 0));
EXPECT_THAT(path(2, 1), ElementsAre(2, 3, 1));
EXPECT_THAT(path(2, 2), ElementsAre(2));
EXPECT_THAT(path(2, 3), ElementsAre(2, 3));
EXPECT_THAT(path(2, 4), ElementsAre(2, 3, 1, 4));
EXPECT_THAT(path(3, 0), ElementsAre(3, 1, 0));
EXPECT_THAT(path(3, 1), ElementsAre(3, 1));
EXPECT_THAT(path(3, 2), ElementsAre(3, 2));
EXPECT_THAT(path(3, 3), ElementsAre(3));
EXPECT_THAT(path(3, 4), ElementsAre(3, 1, 4));
EXPECT_THAT(path(4, 0), ElementsAre(4, 1, 0));
EXPECT_THAT(path(4, 1), ElementsAre(4, 1));
EXPECT_THAT(path(4, 2), ElementsAre(4, 1, 3, 2));
EXPECT_THAT(path(4, 3), ElementsAre(4, 1, 3));
EXPECT_THAT(path(4, 4), ElementsAre(4));
}
TYPED_TEST_P(RootedTreeTest, Distance) {
using Node = TypeParam;
// 4
// /
// 1
// / |
// 0 3
// |
// 2
const int root = 4;
std::vector<Node> parents = {1, 4, 3, 1, this->kNullParent};
std::vector<double> arc_lengths = {1.0, 10.0, 100.0, 1000.0, 0.0};
ASSERT_OK_AND_ASSIGN(const auto tree,
RootedTree<Node>::Create(root, parents));
std::vector<Node> depths = {2, 1, 3, 2, 0};
ASSERT_THAT(tree.AllDepths(), ElementsAreArray(depths));
auto dist = [&tree, &depths, &arc_lengths](Node start, Node end) {
const Node lca = tree.LowestCommonAncestorByDepth(start, end, depths);
return tree.Distance(start, end, lca, arc_lengths);
};
EXPECT_DOUBLE_EQ(dist(0, 0), 0.0);
EXPECT_DOUBLE_EQ(dist(0, 1), 1.0);
EXPECT_DOUBLE_EQ(dist(0, 2), 1101.0);
EXPECT_DOUBLE_EQ(dist(0, 3), 1001.0);
EXPECT_DOUBLE_EQ(dist(0, 4), 11.0);
EXPECT_DOUBLE_EQ(dist(1, 0), 1.0);
EXPECT_DOUBLE_EQ(dist(1, 1), 0.0);
EXPECT_DOUBLE_EQ(dist(1, 2), 1100.0);
EXPECT_DOUBLE_EQ(dist(1, 3), 1000.0);
EXPECT_DOUBLE_EQ(dist(1, 4), 10.0);
EXPECT_DOUBLE_EQ(dist(2, 0), 1101.0);
EXPECT_DOUBLE_EQ(dist(2, 1), 1100.0);
EXPECT_DOUBLE_EQ(dist(2, 2), 0.0);
EXPECT_DOUBLE_EQ(dist(2, 3), 100.0);
EXPECT_DOUBLE_EQ(dist(2, 4), 1110.0);
EXPECT_DOUBLE_EQ(dist(3, 0), 1001.0);
EXPECT_DOUBLE_EQ(dist(3, 1), 1000.0);
EXPECT_DOUBLE_EQ(dist(3, 2), 100.0);
EXPECT_DOUBLE_EQ(dist(3, 3), 0.0);
EXPECT_DOUBLE_EQ(dist(3, 4), 1010.0);
EXPECT_DOUBLE_EQ(dist(4, 0), 11.0);
EXPECT_DOUBLE_EQ(dist(4, 1), 10.0);
EXPECT_DOUBLE_EQ(dist(4, 2), 1110.0);
EXPECT_DOUBLE_EQ(dist(4, 3), 1010.0);
EXPECT_DOUBLE_EQ(dist(4, 4), 0.0);
}
TYPED_TEST_P(RootedTreeTest, EvertChangeRoot) {
using Node = TypeParam;
// Starting graph, with root 2:
// 0 -> 1 -> 2
// | | |
// 3 4 5
//
// Evert: change the root to 0
//
// 0 <- 1 <- 2
// | | |
// 3 4 5
const Node root = 2;
const std::vector<Node> parents = {1, 2, this->kNullParent, 0, 1, 2};
ASSERT_OK_AND_ASSIGN(auto tree, RootedTree<Node>::Create(root, parents));
tree.Evert(0);
EXPECT_EQ(tree.root(), 0);
EXPECT_THAT(tree.parents(), ElementsAre(this->kNullParent, 0, 1, 0, 1, 2));
}
TYPED_TEST_P(RootedTreeTest, EvertSameRoot) {
using Node = TypeParam;
const Node root = 1;
const std::vector<Node> parents = {1, this->kNullParent, 1};
ASSERT_OK_AND_ASSIGN(auto tree, RootedTree<Node>::Create(root, parents));
tree.Evert(1);
EXPECT_EQ(tree.root(), 1);
EXPECT_THAT(tree.parents(), ElementsAre(1, this->kNullParent, 1));
}
TYPED_TEST_P(RootedTreeTest, RootedTreeFromGraphSuccessNoExtraOutputs) {
using Node = TypeParam;
// 4
// /
// 1
// / |
// 0 3
// |
// 2
util::ListGraph<Node, int> graph;
graph.AddNode(4);
for (auto [n1, n2] :
std::vector<std::pair<Node, Node>>{{0, 1}, {1, 4}, {1, 3}, {3, 2}}) {
graph.AddArc(n1, n2);
graph.AddArc(n2, n1);
}
const Node root = 4;
std::vector<Node>* topo = nullptr;
std::vector<Node>* depth = nullptr;
ASSERT_OK_AND_ASSIGN(const RootedTree<Node> tree,
RootedTreeFromGraph(root, graph, topo, depth));
EXPECT_EQ(tree.root(), 4);
EXPECT_THAT(tree.parents(), ElementsAre(1, 4, 3, 1, this->kNullParent));
EXPECT_EQ(topo, nullptr);
EXPECT_EQ(depth, nullptr);
}
TYPED_TEST_P(RootedTreeTest, RootedTreeFromGraphSuccessAllExtraOutputs) {
using Node = TypeParam;
// 4
// /
// 1
// / |
// 0 3
// |
// 2
util::ListGraph<Node, int> graph;
graph.AddNode(4);
for (auto [n1, n2] :
std::vector<std::pair<Node, Node>>{{0, 1}, {1, 4}, {1, 3}, {3, 2}}) {
graph.AddArc(n1, n2);
graph.AddArc(n2, n1);
}
const Node root = 4;
std::vector<Node> topo;
std::vector<Node> depth;
ASSERT_OK_AND_ASSIGN(const RootedTree<Node> tree,
RootedTreeFromGraph(root, graph, &topo, &depth));
EXPECT_EQ(tree.root(), 4);
EXPECT_THAT(tree.parents(), ElementsAre(1, 4, 3, 1, this->kNullParent));
EXPECT_THAT(topo,
AnyOf(ElementsAre(4, 1, 0, 3, 2), ElementsAre(4, 1, 3, 0, 2),
ElementsAre(4, 1, 3, 2, 0)));
EXPECT_THAT(depth, ElementsAre(2, 1, 3, 2, 0));
}
TYPED_TEST_P(RootedTreeTest, RootedTreeFromGraphBadRootInvalidArgument) {
using Node = TypeParam;
util::ListGraph<Node, int> graph;
graph.AddNode(2);
graph.AddArc(0, 1);
graph.AddArc(1, 0);
const Node root = 4;
EXPECT_THAT(
RootedTreeFromGraph(root, graph, nullptr, nullptr),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("invalid root")));
}
TYPED_TEST_P(RootedTreeTest, RootedTreeFromGraphSelfCycleInvalidArgument) {
using Node = TypeParam;
util::ListGraph<Node, int> graph;
graph.AddNode(2);
graph.AddArc(0, 1);
graph.AddArc(1, 0);
graph.AddArc(1, 1);
const Node root = 0;
EXPECT_THAT(RootedTreeFromGraph(root, graph, nullptr, nullptr),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("cycle")));
}
TYPED_TEST_P(RootedTreeTest, RootedTreeFromGraphHasCycleInvalidArgument) {
using Node = TypeParam;
util::ListGraph<Node, int> graph;
graph.AddNode(3);
graph.AddArc(0, 1);
graph.AddArc(1, 0);
graph.AddArc(1, 2);
graph.AddArc(2, 1);
graph.AddArc(2, 0);
graph.AddArc(0, 2);
const Node root = 0;
EXPECT_THAT(RootedTreeFromGraph(root, graph, nullptr, nullptr),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("cycle")));
}
TYPED_TEST_P(RootedTreeTest, RootedTreeFromGraphNotConnectedInvalidArgument) {
using Node = TypeParam;
util::ListGraph<Node, int> graph;
graph.AddNode(4);
graph.AddArc(0, 1);
graph.AddArc(1, 0);
graph.AddArc(2, 3);
graph.AddArc(3, 2);
const Node root = 0;
EXPECT_THAT(
RootedTreeFromGraph(root, graph, nullptr, nullptr),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("not connected")));
}
REGISTER_TYPED_TEST_SUITE_P(
RootedTreeTest, CreateFailsRootOutOfBoundsInvalidArgument,
CreateFailsRootHasParentInvalidArgument,
CreateFailsExtraRootInvalidArgument, CreateFailsBadParentInvalidArgument,
CreateFailsIsolatedCycleInvalidArgument,
CreateFailsPathLeadsToCycleInvalidArgument,
CreatePathFailsLongCycleErrorIsTruncated, PathToRoot, DistanceToRoot,
DistanceAndPathToRoot, TopologicalSort, AllDistancesToRoot, AllDepths,
LCAByDepth, LCAByBySearch, Path, Distance, EvertChangeRoot, EvertSameRoot,
RootedTreeFromGraphSuccessNoExtraOutputs,
RootedTreeFromGraphSuccessAllExtraOutputs,
RootedTreeFromGraphBadRootInvalidArgument,
RootedTreeFromGraphSelfCycleInvalidArgument,
RootedTreeFromGraphHasCycleInvalidArgument,
RootedTreeFromGraphNotConnectedInvalidArgument);
using NodeTypes =
::testing::Types<int16_t, int32_t, int64_t, uint16_t, uint32_t, uint64_t>;
INSTANTIATE_TYPED_TEST_SUITE_P(AllRootedTreeTests, RootedTreeTest, NodeTypes);
////////////////////////////////////////////////////////////////////////////////
// Benchmarks
////////////////////////////////////////////////////////////////////////////////
std::vector<int> RandomTreeRootedZero(int num_nodes) {
absl::BitGen bit_gen;
std::vector<int> nodes(num_nodes);
absl::c_iota(nodes, 0);
std::shuffle(nodes.begin() + 1, nodes.end(), bit_gen);
std::vector<int> result(num_nodes);
result[0] = -1;
for (int i = 1; i < num_nodes; ++i) {
int target = absl::Uniform<int>(bit_gen, 0, i);
result[i] = target;
}
return result;
}
void BM_RootedTreeShortestPath(benchmark::State& state) {
const int num_nodes = state.range(0);
std::vector<int> random_tree_data = RandomTreeRootedZero(num_nodes);
for (auto s : state) {
const RootedTree<int> tree =
RootedTree<int>::Create(0, random_tree_data).value();
std::vector<int> path = tree.PathToRoot(num_nodes - 1);
CHECK_GE(path.size(), 2);
}
}
BENCHMARK(BM_RootedTreeShortestPath)->Arg(100)->Arg(10'000)->Arg(1'000'000);
} // namespace
} // namespace operations_research

View File

@@ -37,6 +37,16 @@ code_sample_cc(name = "dag_shortest_path_sequential")
code_sample_cc(name = "dag_simple_shortest_path")
code_sample_cc(name = "dag_multiple_shortest_paths_one_to_all")
code_sample_cc(name = "dag_multiple_shortest_paths_sequential")
code_sample_cc(name = "dag_simple_multiple_shortest_paths")
code_sample_cc(name = "dag_constrained_shortest_path_sequential")
code_sample_cc(name = "dag_simple_constrained_shortest_path")
code_sample_cc(name = "dijkstra_all_pairs_shortest_paths")
code_sample_cc(name = "dijkstra_directed")
@@ -47,6 +57,10 @@ code_sample_cc(name = "dijkstra_sequential")
code_sample_cc(name = "dijkstra_undirected")
code_sample_cc(name = "root_a_tree")
code_sample_cc(name = "rooted_tree_paths")
code_sample_java(name = "SimpleMaxFlowProgram")
code_sample_cc_py(name = "simple_max_flow_program")

View File

@@ -26,11 +26,13 @@ def code_sample_cc(name):
"//ortools/graph:assignment",
"//ortools/graph:bounded_dijkstra",
"//ortools/graph:bfs",
"//ortools/graph:dag_constrained_shortest_path",
"//ortools/graph:dag_shortest_path",
"//ortools/graph:ebert_graph",
"//ortools/graph:linear_assignment",
"//ortools/graph:max_flow",
"//ortools/graph:min_cost_flow",
"//ortools/graph:rooted_tree",
"@com_google_absl//absl/random",
],
)
@@ -47,11 +49,13 @@ def code_sample_cc(name):
"//ortools/graph:assignment",
"//ortools/graph:bounded_dijkstra",
"//ortools/graph:bfs",
"//ortools/graph:dag_constrained_shortest_path",
"//ortools/graph:dag_shortest_path",
"//ortools/graph:ebert_graph",
"//ortools/graph:linear_assignment",
"//ortools/graph:max_flow",
"//ortools/graph:min_cost_flow",
"//ortools/graph:rooted_tree",
"@com_google_absl//absl/random",
],
)

View File

@@ -0,0 +1,138 @@
// Copyright 2010-2024 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.
// [START imports]
#include <cstdint>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/graph/dag_constrained_shortest_path.h"
#include "ortools/graph/dag_shortest_path.h"
#include "ortools/graph/graph.h"
// [END imports]
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
// [START graph]
// Create a graph with n + 2 nodes, indexed from 0:
// * Node n is `source`
// * Node n+1 is `dest`
// * Nodes M = [0, 1, ..., n-1] are in the middle.
//
// There is a single resource constraints with limit 1.
//
// The graph has 3 * n - 1 arcs (with weights and both resources):
// * (source -> i) with weight 100 and no resource use for i in M
// * (i -> dest) with weight 100 and no resource use for i in M
// * (i -> (i+1)) with weight 1 and resource use of 1 for i = 0, ..., n-2
//
// Every path [source, i, dest] for i in M is a constrained shortest path from
// source to dest with weight 200.
const int n = 5;
const int source = n;
const int dest = n + 1;
const int num_arcs = 3 * n - 1;
util::StaticGraph<> graph;
// There are 3 types of arcs: (1) source to M, (2) M to dest, and (3) within
// M. This vector stores all of them, first of type (1), then type (2),
// then type (3). The arcs are ordered by i in M within each type.
std::vector<double> weights(num_arcs);
// Resources are first indexed by resource, then by arc.
std::vector<std::vector<double>> resources(1, std::vector<double>(num_arcs));
for (int i = 0; i < n; ++i) {
graph.AddArc(source, i);
weights[i] = 100.0;
resources[0][i] = 0.0;
}
for (int i = 0; i < n; ++i) {
graph.AddArc(i, dest);
weights[n + i] = 100.0;
resources[0][n + i] = 0.0;
}
for (int i = 0; i + 1 < n; ++i) {
graph.AddArc(i, i + 1);
weights[2 * n + i] = 1.0;
resources[0][2 * n + i] = 1.0;
}
// Static graph reorders the arcs at Build() time, use permutation to get from
// the old ordering to the new one.
std::vector<int32_t> permutation;
graph.Build(&permutation);
util::Permute(permutation, &weights);
util::Permute(permutation, &resources[0]);
// [END graph]
// [START first-path]
// A reusable shortest path calculator.
// We need a topological order. For this structured graph, we find it by hand
// instead of using util::graph::FastTopologicalSort().
std::vector<int32_t> topological_order = {source};
for (int32_t i = 0; i < n; ++i) {
topological_order.push_back(i);
}
topological_order.push_back(dest);
const std::vector<int> sources = {source};
const std::vector<int> destinations = {dest};
const std::vector<double> max_resources = {1.0};
operations_research::ConstrainedShortestPathsOnDagWrapper<util::StaticGraph<>>
constrained_shortest_path_on_dag(&graph, &weights, &resources,
topological_order, sources, destinations,
&max_resources);
operations_research::PathWithLength initial_constrained_shortest_path =
constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag();
std::cout << "Initial distance: " << initial_constrained_shortest_path.length
<< std::endl;
std::cout << "Initial path: "
<< absl::StrJoin(initial_constrained_shortest_path.node_path, ", ")
<< std::endl;
// [END first-path]
// [START more-paths]
// Now, we make a single arc from source to M free, and a single arc from M
// to dest free, and resolve. If the free edge from the source hits before
// the free edge to the dest in M, we use both, walking through M. Otherwise,
// we use only one free arc.
std::vector<std::pair<int, int>> fast_paths = {{2, 3}, {8, 1}, {3, 7}};
for (const auto [free_from_source, free_to_dest] : fast_paths) {
weights[permutation[free_from_source]] = 0;
weights[permutation[n + free_to_dest]] = 0;
operations_research::PathWithLength constrained_shortest_path =
constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag();
std::cout << "source -> " << free_from_source << " and " << free_to_dest
<< " -> dest are now free" << std::endl;
std::string label = absl::StrCat("_", free_from_source, "_", free_to_dest);
std::cout << "Distance" << label << ": " << constrained_shortest_path.length
<< std::endl;
std::cout << "Path" << label << ": "
<< absl::StrJoin(constrained_shortest_path.node_path, ", ")
<< std::endl;
// Restore the old weights
weights[permutation[free_from_source]] = 100;
weights[permutation[n + free_to_dest]] = 100;
}
// [END more-paths]
return 0;
}

View File

@@ -0,0 +1,87 @@
// Copyright 2010-2024 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.
#include <cstdint>
#include <iostream>
#include <vector>
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/base/status_macros.h"
#include "ortools/graph/dag_shortest_path.h"
#include "ortools/graph/graph.h"
#include "ortools/graph/topologicalsorter.h"
namespace {
absl::Status Main() {
util::StaticGraph<> graph;
std::vector<double> weights;
graph.AddArc(0, 1);
weights.push_back(2.0);
graph.AddArc(0, 2);
weights.push_back(5.0);
graph.AddArc(1, 4);
weights.push_back(1.0);
graph.AddArc(2, 4);
weights.push_back(-3.0);
graph.AddArc(3, 4);
weights.push_back(0.0);
// Static graph reorders the arcs at Build() time, use permutation to get
// from the old ordering to the new one.
std::vector<int32_t> permutation;
graph.Build(&permutation);
util::Permute(permutation, &weights);
// We need a topological order. We can find it by hand on this small graph,
// e.g., {0, 1, 2, 3, 4}, but we demonstrate how to compute one instead.
ASSIGN_OR_RETURN(const std::vector<int32_t> topological_order,
util::graph::FastTopologicalSort(graph));
operations_research::KShortestPathsOnDagWrapper<util::StaticGraph<>>
shortest_paths_on_dag(&graph, &weights, topological_order,
/*path_count=*/2);
const int source = 0;
shortest_paths_on_dag.RunKShortestPathOnDag({source});
// For each node other than 0, print its distance and the shortest path.
for (int node = 1; node < 5; ++node) {
std::cout << "Node " << node << ":\n";
if (!shortest_paths_on_dag.IsReachable(node)) {
std::cout << "\tNo path to node " << node << std::endl;
continue;
}
const std::vector<double> lengths = shortest_paths_on_dag.LengthsTo(node);
const std::vector<std::vector<int32_t>> paths =
shortest_paths_on_dag.NodePathsTo(node);
for (int path_index = 0; path_index < lengths.size(); ++path_index) {
std::cout << "\t#" << (path_index + 1) << " shortest path to node "
<< node << " has length: " << lengths[path_index] << std::endl;
std::cout << "\t#" << (path_index + 1) << " shortest path to node "
<< node << " is: " << absl::StrJoin(paths[path_index], ", ")
<< std::endl;
}
}
return absl::OkStatus();
}
} // namespace
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
QCHECK_OK(Main());
return 0;
}

View File

@@ -0,0 +1,135 @@
// Copyright 2010-2024 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.
// [START imports]
#include <cstdint>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/graph/dag_shortest_path.h"
#include "ortools/graph/graph.h"
// [END imports]
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
// [START graph]
// Create a graph with n + 2 nodes, indexed from 0:
// * Node n is `source`
// * Node n+1 is `dest`
// * Nodes M = [0, 1, ..., n-1] are in the middle.
//
// The graph has 3 * n - 1 arcs (with weights):
// * (source -> i) with weight 100 + i for i in M
// * (i -> dest) with weight 100 + i for i in M
// * (i -> (i+1)) with weight 10 for i = 0, ..., n-2
const int n = 10;
const int source = n;
const int dest = n + 1;
util::StaticGraph<> graph;
// There are 3 types of arcs: (1) source to M, (2) M to dest, and (3) within
// M. This vector stores all of them, first of type (1), then type (2),
// then type (3). The arcs are ordered by i in M within each type.
std::vector<double> weights(3 * n - 1);
for (int i = 0; i < n; ++i) {
graph.AddArc(source, i);
weights[i] = 100.0 + i;
}
for (int i = 0; i < n; ++i) {
graph.AddArc(i, dest);
weights[n + i] = 100.0 + i;
}
for (int i = 0; i + 1 < n; ++i) {
graph.AddArc(i, i + 1);
weights[2 * n + i] = 10.0;
}
// Static graph reorders the arcs at Build() time, use permutation to get from
// the old ordering to the new one.
std::vector<int32_t> permutation;
graph.Build(&permutation);
util::Permute(permutation, &weights);
// [END graph]
// [START first-path]
// A reusable shortest path calculator.
// We need a topological order. For this structured graph, we find it by hand
// instead of using util::graph::FastTopologicalSort().
std::vector<int32_t> topological_order = {source};
for (int32_t i = 0; i < n; ++i) {
topological_order.push_back(i);
}
topological_order.push_back(dest);
operations_research::KShortestPathsOnDagWrapper<util::StaticGraph<>>
shortest_paths_on_dag(&graph, &weights, topological_order,
/*path_count=*/2);
shortest_paths_on_dag.RunKShortestPathOnDag({source});
const std::vector<double> initial_lengths =
shortest_paths_on_dag.LengthsTo(dest);
const std::vector<std::vector<int32_t>> initial_paths =
shortest_paths_on_dag.NodePathsTo(dest);
std::cout << "No free arcs" << std::endl;
for (int path_index = 0; path_index < initial_lengths.size(); ++path_index) {
std::cout << "\t#" << (path_index + 1)
<< " shortest path has length: " << initial_lengths[path_index]
<< std::endl;
std::cout << "\t#" << (path_index + 1) << " shortest path is: "
<< absl::StrJoin(initial_paths[path_index], ", ") << std::endl;
}
// [END first-path]
// [START more-paths]
// Now, we make a single arc from source to M free, and a single arc from M
// to dest free, and resolve. If the free edge from the source hits before
// the free edge to the dest in M, we use both, walking through M. Otherwise,
// we use only one free arc.
std::vector<std::pair<int, int>> fast_paths = {
{2, 4}, {8, 1}, {3, 3}, {0, 0}};
for (const auto [free_from_source, free_to_dest] : fast_paths) {
weights[permutation[free_from_source]] = 0;
weights[permutation[n + free_to_dest]] = 0;
shortest_paths_on_dag.RunKShortestPathOnDag({source});
std::cout << "source -> " << free_from_source << " and " << free_to_dest
<< " -> dest are now free" << std::endl;
std::string label =
absl::StrCat(" (", free_from_source, ", ", free_to_dest, ")");
const std::vector<double> lengths = shortest_paths_on_dag.LengthsTo(dest);
const std::vector<std::vector<int32_t>> paths =
shortest_paths_on_dag.NodePathsTo(dest);
for (int path_index = 0; path_index < lengths.size(); ++path_index) {
std::cout << "\t#" << (path_index + 1) << " shortest path" << label
<< " has length: " << lengths[path_index] << std::endl;
std::cout << "\t#" << (path_index + 1) << " shortest path" << label
<< " is: " << absl::StrJoin(paths[path_index], ", ")
<< std::endl;
}
// Restore the old weights
weights[permutation[free_from_source]] = 100 + free_from_source;
weights[permutation[n + free_to_dest]] = 100 + free_to_dest;
}
// [END more-paths]
return 0;
}

View File

@@ -0,0 +1,47 @@
// Copyright 2010-2024 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.
#include <iostream>
#include <vector>
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/graph/dag_constrained_shortest_path.h"
#include "ortools/graph/dag_shortest_path.h"
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
// The input graph, encoded as a list of arcs with distances.
std::vector<operations_research::ArcWithLengthAndResources> arcs = {
{.from = 0, .to = 1, .length = 5, .resources = {1, 2}},
{.from = 0, .to = 2, .length = 4, .resources = {3, 2}},
{.from = 0, .to = 2, .length = 1, .resources = {2, 3}},
{.from = 1, .to = 3, .length = -3, .resources = {8, 0}},
{.from = 2, .to = 3, .length = 0, .resources = {3, 1}}};
const int num_nodes = 4;
const std::vector<double> max_resources = {6, 3};
const int source = 0;
const int destination = 3;
const operations_research::PathWithLength path_with_length =
operations_research::ConstrainedShortestPathsOnDag(
num_nodes, arcs, source, destination, max_resources);
// Print to length of the path and then the nodes in the path.
std::cout << "Constrained shortest path length: " << path_with_length.length
<< std::endl;
std::cout << "Constrained shortest path nodes: "
<< absl::StrJoin(path_with_length.node_path, ", ") << std::endl;
return 0;
}

View File

@@ -0,0 +1,47 @@
// Copyright 2010-2024 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.
#include <iostream>
#include <vector>
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/graph/dag_shortest_path.h"
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
// The input graph, encoded as a list of arcs with distances.
std::vector<operations_research::ArcWithLength> arcs = {
{.from = 0, .to = 1, .length = 2}, {.from = 0, .to = 2, .length = 5},
{.from = 0, .to = 3, .length = 4}, {.from = 1, .to = 4, .length = 1},
{.from = 2, .to = 4, .length = -3}, {.from = 3, .to = 4, .length = 0}};
const int num_nodes = 5;
const int source = 0;
const int destination = 4;
const int path_count = 2;
const std::vector<operations_research::PathWithLength> paths_with_length =
operations_research::KShortestPathsOnDag(num_nodes, arcs, source,
destination, path_count);
for (int path_index = 0; path_index < paths_with_length.size();
++path_index) {
std::cout << "#" << (path_index + 1) << " shortest path has length: "
<< paths_with_length[path_index].length << std::endl;
std::cout << "#" << (path_index + 1) << " shortest path is: "
<< absl::StrJoin(paths_with_length[path_index].node_path, ", ")
<< std::endl;
}
return 0;
}

View File

@@ -23,11 +23,11 @@ int main(int argc, char** argv) {
// The input graph, encoded as a list of arcs with distances.
std::vector<operations_research::ArcWithLength> arcs = {
{.tail = 0, .head = 2, .length = 5},
{.tail = 0, .head = 3, .length = 4},
{.tail = 1, .head = 3, .length = 1},
{.tail = 2, .head = 4, .length = -3},
{.tail = 3, .head = 4, .length = 0}};
{.from = 0, .to = 2, .length = 5},
{.from = 0, .to = 3, .length = 4},
{.from = 1, .to = 3, .length = 1},
{.from = 2, .to = 4, .length = -3},
{.from = 3, .to = 4, .length = 0}};
const int num_nodes = 5;
const int source = 0;

View File

@@ -0,0 +1,86 @@
// Copyright 2010-2024 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.
#include <cstdint>
#include <iostream>
#include <utility>
#include <vector>
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/base/status_macros.h"
#include "ortools/graph/graph.h"
#include "ortools/graph/rooted_tree.h"
namespace {
absl::Status Main() {
// Make an undirected tree as a graph using ListGraph (add the arcs in each
// direction).
const int32_t num_nodes = 5;
std::vector<std::pair<int32_t, int32_t>> arcs = {
{0, 1}, {1, 2}, {2, 3}, {1, 4}};
util::ListGraph<> graph(num_nodes, 2 * static_cast<int32_t>(arcs.size()));
for (const auto [s, t] : arcs) {
graph.AddArc(s, t);
graph.AddArc(t, s);
}
// Root the tree from 2. Save the depth of each node and topological ordering
int root = 2;
std::vector<int32_t> topological_order;
std::vector<int32_t> depth;
ASSIGN_OR_RETURN(const operations_research::RootedTree<int32_t> tree,
operations_research::RootedTreeFromGraph(
root, graph, &topological_order, &depth));
// Parents are:
// 0 -> 1
// 1 -> 2
// 2 is root (returns -1)
// 3 -> 2
// 4 -> 1
std::cout << "Parents:" << std::endl;
for (int i = 0; i < num_nodes; ++i) {
std::cout << " " << i << " -> " << tree.parents()[i] << std::endl;
}
// Depths are:
// 0: 2
// 1: 1
// 2: 0
// 3: 1
// 4: 2
std::cout << "Depths:" << std::endl;
for (int i = 0; i < num_nodes; ++i) {
std::cout << " " << i << " -> " << depth[i] << std::endl;
}
// Many possible topological orders, including:
// [2, 1, 0, 4, 3]
// all starting with 2.
std::cout << "Topological order: " << absl::StrJoin(topological_order, ", ")
<< std::endl;
return absl::OkStatus();
}
} // namespace
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
QCHECK_OK(Main());
return 0;
}

View File

@@ -0,0 +1,58 @@
// Copyright 2010-2024 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.
#include <iostream>
#include <vector>
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/strings/str_join.h"
#include "ortools/base/init_google.h"
#include "ortools/base/status_macros.h"
#include "ortools/graph/rooted_tree.h"
namespace {
absl::Status Main() {
// Make an rooted tree on 5 nodes with root 2 and the parental args:
// 0 -> 1
// 1 -> 2
// 2 is root
// 3 -> 2
// 4 -> 1
ASSIGN_OR_RETURN(
const operations_research::RootedTree<int> tree,
operations_research::RootedTree<int>::Create(2, {1, 2, -1, 2, 1}));
// Precompute this for LCA computations below.
const std::vector<int> depths = tree.AllDepths();
// Find the path between every pair of nodes in the tree.
for (int s = 0; s < 5; ++s) {
for (int t = 0; t < 5; ++t) {
int lca = tree.LowestCommonAncestorByDepth(s, t, depths);
const std::vector<int> path = tree.Path(s, t, lca);
std::cout << s << " -> " << t << " [" << absl::StrJoin(path, ", ") << "]"
<< std::endl;
}
}
return absl::OkStatus();
}
} // namespace
int main(int argc, char** argv) {
InitGoogle(argv[0], &argc, &argv, true);
QCHECK_OK(Main());
return 0;
}