graph: Export from google3
* Add rooted_tree.h * Add few samples * Clean includes
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#ifndef OR_TOOLS_GRAPH_CLIQUES_H_
|
||||
#define OR_TOOLS_GRAPH_CLIQUES_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)};
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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?
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
802
ortools/graph/rooted_tree.h
Normal 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_
|
||||
653
ortools/graph/rooted_tree_test.cc
Normal file
653
ortools/graph/rooted_tree_test.cc
Normal 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
|
||||
@@ -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")
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
135
ortools/graph/samples/dag_multiple_shortest_paths_sequential.cc
Normal file
135
ortools/graph/samples/dag_multiple_shortest_paths_sequential.cc
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
47
ortools/graph/samples/dag_simple_multiple_shortest_paths.cc
Normal file
47
ortools/graph/samples/dag_simple_multiple_shortest_paths.cc
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
86
ortools/graph/samples/root_a_tree.cc
Normal file
86
ortools/graph/samples/root_a_tree.cc
Normal 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;
|
||||
}
|
||||
58
ortools/graph/samples/rooted_tree_paths.cc
Normal file
58
ortools/graph/samples/rooted_tree_paths.cc
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user