graph: backport from main

This commit is contained in:
Corentin Le Molgat
2024-03-25 11:21:53 +01:00
parent 5bdcc38cbd
commit bd97b225bd
17 changed files with 941 additions and 86 deletions

View File

@@ -11,7 +11,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
load("@rules_cc//cc:defs.bzl", "cc_proto_library")
load("@rules_proto//proto:defs.bzl", "proto_library")
load("@rules_cc//cc:defs.bzl", "cc_library", "cc_proto_library")
package(default_visibility = ["//visibility:public"])
@@ -65,10 +66,12 @@ cc_library(
hdrs = ["bounded_dijkstra.h"],
deps = [
":graph",
"//ortools/base",
"//ortools/base:iterator_adaptors",
"//ortools/base:threadpool",
"@com_google_absl//absl/algorithm:container",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/log:check",
"@com_google_absl//absl/types:span",
],
)
@@ -76,8 +79,9 @@ cc_library(
name = "multi_dijkstra",
hdrs = ["multi_dijkstra.h"],
deps = [
"//ortools/base",
"@com_google_absl//absl/container:flat_hash_set",
"//ortools/base:map_util",
"//ortools/base:types",
"@com_google_absl//absl/container:flat_hash_map",
],
)
@@ -86,6 +90,7 @@ cc_library(
hdrs = ["bidirectional_dijkstra.h"],
deps = [
"//ortools/base",
"//ortools/base:iterator_adaptors",
"//ortools/base:threadpool",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/strings",
@@ -99,7 +104,7 @@ cc_library(
hdrs = ["cliques.h"],
deps = [
"//ortools/base",
"//ortools/base:intops",
"//ortools/base:int_type",
"//ortools/base:strong_vector",
"//ortools/util:time_limit",
"@com_google_absl//absl/container:flat_hash_set",
@@ -112,6 +117,7 @@ cc_library(
hdrs = ["hamiltonian_path.h"],
deps = [
"//ortools/base",
"//ortools/base:types",
"//ortools/util:bitset",
"//ortools/util:saturated_arithmetic",
"//ortools/util:vector_or_function",
@@ -123,11 +129,16 @@ cc_library(
hdrs = ["christofides.h"],
deps = [
":eulerian_path",
":graph",
":minimum_spanning_tree",
":perfect_matching",
"//ortools/base",
"//ortools/base:types",
"//ortools/linear_solver",
"//ortools/linear_solver:linear_solver_cc_proto",
"//ortools/util:saturated_arithmetic",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
],
)
@@ -144,10 +155,10 @@ cc_library(
hdrs = ["minimum_spanning_tree.h"],
deps = [
":connected_components",
":graph",
"//ortools/base",
"//ortools/base:adjustable_priority_queue",
"//ortools/base:types",
"//ortools/util:vector_or_function",
"@com_google_absl//absl/types:span",
],
)
@@ -157,8 +168,8 @@ cc_library(
deps = [
":christofides",
":minimum_spanning_tree",
"//ortools/base",
"@com_google_absl//absl/strings",
"//ortools/base:types",
"@com_google_absl//absl/types:span",
],
)
@@ -167,8 +178,10 @@ cc_library(
hdrs = ["ebert_graph.h"],
deps = [
"//ortools/base",
"//ortools/base:types",
"//ortools/util:permutation",
"//ortools/util:zvector",
"@com_google_absl//absl/strings",
],
)
@@ -181,12 +194,29 @@ cc_library(
":graph",
"//ortools/base",
"//ortools/base:adjustable_priority_queue",
"//ortools/base:file",
"//ortools/base:map_util",
"//ortools/base:stl_util",
"//ortools/base:threadpool",
"//ortools/base:timer",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/functional:bind_front",
"@com_google_absl//absl/log",
"@com_google_absl//absl/log:check",
"@com_google_absl//absl/types:span",
],
)
cc_library(
name = "k_shortest_paths",
hdrs = ["k_shortest_paths.h"],
deps = [
":bounded_dijkstra",
":ebert_graph",
":shortest_paths",
"@com_google_absl//absl/algorithm:container",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/log:check",
"@com_google_absl//absl/types:span",
],
)
@@ -212,9 +242,12 @@ cc_library(
":graph",
":graphs",
"//ortools/base",
"//ortools/base:types",
"//ortools/util:stats",
"//ortools/util:zvector",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
],
)
@@ -228,17 +261,15 @@ cc_test(
":graphs",
":max_flow",
"//ortools/base",
"//ortools/base:gmock",
"//ortools/base:message_matchers",
"//ortools/base:gmock_main",
"//ortools/base:path",
"//ortools/base:status_matchers",
"//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",
"@com_google_googletest//:gtest_main",
"@com_google_protobuf//:protobuf",
],
)
@@ -254,7 +285,6 @@ cc_library(
"//conditions:default": [],
}),
deps = [
":connected_components",
":ebert_graph",
":graph",
":graphs",
@@ -262,9 +292,13 @@ cc_library(
"//ortools/base",
"//ortools/base:dump_vars",
"//ortools/base:mathutil",
"//ortools/base:types",
"//ortools/util:saturated_arithmetic",
"//ortools/util:stats",
"//ortools/util:zvector",
"@com_google_absl//absl/flags:flag",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
],
)
@@ -279,7 +313,8 @@ cc_binary(
":min_cost_flow",
"//ortools/base",
"//ortools/base:file",
"//ortools/base:filesystem",
"//ortools/base:status_macros",
"//ortools/base:timer",
"//ortools/util:filelineiter",
"//ortools/util:stats",
"@com_google_absl//absl/flags:flag",
@@ -298,7 +333,7 @@ cc_library(
deps = [
":ebert_graph",
":linear_assignment",
"//ortools/base",
"@com_google_absl//absl/flags:flag",
],
)
@@ -314,50 +349,11 @@ cc_library(
"//ortools/base:types",
"//ortools/util:permutation",
"//ortools/util:zvector",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/flags:flag",
"@com_google_absl//absl/strings:str_format",
],
)
# Biconnected
#cc_library(
# name = "biconnected",
# srcs = ["biconnected.cc"],
# hdrs = ["biconnected.h"],
# deps = [
# ":ebert_graph",
# "//ortools/base",
# "//ortools/base:types",
# ],
#)
# Hopcroft-Karp (Old)
#cc_library(
# name = "hopcroft_karp",
# srcs = ["hopcroft_karp.c"],
# hdrs = ["hopcroft_karp.h"],
#)
# Hopcroft-Karp (New)
#cc_library(
# name = "bipartite_matching",
# srcs = ["bipartite_matching.cc"],
# hdrs = ["bipartite_matching.h"],
# deps = [
# "//ortools/base",
# ],
#)
#cc_library(
# name = "dag_connectivity",
# srcs = ["dag_connectivity.cc"],
# hdrs = ["dag_connectivity.h"],
# deps = [
# ":topologicalsorter",
# "//ortools/base",
# ],
#)
cc_library(
name = "perfect_matching",
srcs = ["perfect_matching.cc"],
@@ -365,7 +361,7 @@ cc_library(
deps = [
"//ortools/base",
"//ortools/base:adjustable_priority_queue",
"//ortools/base:intops",
"//ortools/base:int_type",
"//ortools/base:strong_vector",
"//ortools/base:types",
"//ortools/util:saturated_arithmetic",
@@ -382,6 +378,8 @@ cc_library(
deps = [
":graph",
":topologicalsorter",
"@com_google_absl//absl/algorithm:container",
"@com_google_absl//absl/base:log_severity",
"@com_google_absl//absl/log",
"@com_google_absl//absl/log:check",
"@com_google_absl//absl/status",
@@ -392,12 +390,16 @@ cc_library(
cc_library(
name = "dag_constrained_shortest_path",
testonly = True,
srcs = ["dag_constrained_shortest_path.cc"],
hdrs = ["dag_constrained_shortest_path.h"],
deps = [
":dag_shortest_path",
":graph",
":topologicalsorter",
"@com_google_absl//absl/algorithm:container",
"@com_google_absl//absl/base:log_severity",
"@com_google_absl//absl/log:check",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/types:span",
@@ -441,6 +443,21 @@ cc_library(
hdrs = ["iterators.h"],
)
cc_library(
name = "random_graph",
srcs = ["random_graph.cc"],
hdrs = ["random_graph.h"],
deps = [
":graph",
"//ortools/base:logging",
"//ortools/base:types",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/random",
"@com_google_absl//absl/random:bit_gen_ref",
],
)
cc_library(
name = "strongly_connected_components",
hdrs = [

View File

@@ -23,6 +23,7 @@ list(REMOVE_ITEM _SRCS
${CMAKE_CURRENT_SOURCE_DIR}/ebert_graph_test.cc
${CMAKE_CURRENT_SOURCE_DIR}/eulerian_path_test.cc
${CMAKE_CURRENT_SOURCE_DIR}/hamiltonian_path_test.cc
${CMAKE_CURRENT_SOURCE_DIR}/k_shortest_paths_test.cc
${CMAKE_CURRENT_SOURCE_DIR}/linear_assignment_test.cc
${CMAKE_CURRENT_SOURCE_DIR}/max_flow_test.cc
${CMAKE_CURRENT_SOURCE_DIR}/min_cost_flow_test.cc

View File

@@ -6,8 +6,8 @@ network flow problems.
It contains in particular:
* well-tuned algorithms (for example, shortest paths and
[Hamiltonian paths](https://en.wikipedia.org/wiki/Hamiltonian_path)).
* hard-to-find algorithms (Hamiltonian paths, push-relabel flow algorithms).
[Hamiltonian paths](https://en.wikipedia.org/wiki/Hamiltonian_path)).
* hard-to-find algorithms (Hamiltonian paths, push-relabel flow algorithms).
* other, more common algorithms, that are useful to use with `EbertGraph`.
Graph representations:
@@ -69,11 +69,11 @@ Flow algorithms:
* [`linear_assignment.h`][linear_assignment_h]: entry point for solving linear
sum assignment problems (classical assignment problems where the total cost
is the sum of the costs of each arc used) on directed graphs with arc costs,
based on the Goldberg-Kennedy push-relabel algorithm.
based on the Goldberg-Kennedy push-relabel algorithm.
* [`max_flow.h`][max_flow_h]: entry point for computing maximum flows on
directed graphs with arc capacities, based on the Goldberg-Tarjan
push-relabel algorithm.
directed graphs with arc capacities, based on the Goldberg-Tarjan
push-relabel algorithm.
* [`min_cost_flow.h`][min_cost_flow_h]: entry point for computing minimum-cost
flows on directed graphs with arc capacities, arc costs, and

View File

@@ -20,15 +20,14 @@
#include <utility>
#include <vector>
#include "absl/base/log_severity.h"
#include "absl/container/flat_hash_map.h"
#include "absl/random/distributions.h"
#include "absl/strings/str_cat.h"
#include "gtest/gtest.h"
#include "ortools/base/gmock.h"
#include "ortools/base/map_util.h"
#include "ortools/graph/bounded_dijkstra.h"
#include "ortools/graph/graph.h"
#include "util/tuple/dump_vars.h"
namespace operations_research {
namespace {
@@ -100,9 +99,6 @@ TEST(BidirectionalDijkstraTest, SmallTest) {
TEST(BidirectionalDijkstraTest, RandomizedCorrectnessTest) {
std::mt19937 random(12345);
// Performance on forge as of 2016-10-05 with these numbers, over 1000 runs:
// - fastbuild: max = 21.9s, avg = 10.7s.
// - opt: max = 23.2s, avg = 10.4s.
const int kNumGraphs = DEBUG_MODE ? 100 : 300;
const int kNumQueriesPerGraph = DEBUG_MODE ? 10 : 30;
const int kNumNodes = 1000;

View File

@@ -0,0 +1,444 @@
// 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.
// Algorithms to compute k-shortest paths. Currently, only Yen's algorithm is
// implemented.
//
// TODO(user): implement Lawler's modification:
// https://pubsonline.informs.org/doi/abs/10.1287/mnsc.18.7.401
//
// | Algo. | Neg. weights | Neg.-weight loops | Graph type | Loopless paths |
// |-------|--------------|-------------------|--------------|----------------|
// | Yen | No | No | (Un)directed | Yes |
//
//
// Design choices
// ==============
//
// The design takes some inspiration from `shortest_paths.h` and
// `bounded_dijkstra.h`, but the shortest-path and k-shortest-path problems have
// vastly different structures.
// For instance, a path container that only stores distances, like
// `DistanceContainer` in `shortest_paths.h`, is irrelevant as an output for
// this problem: it can only characterize one path, the shortest one.
// This is why the results are stored in an intermediate structure, containing
// the paths (as a sequence of nodes, just like `PathContainerImpl` subclasses)
// and their distance.
//
// Only the one-to-one k-shortest-path problem is well-defined. Variants with
// multiple sources and/or destinations pose representational challenges whose
// solution is likely to be algorithm-dependent.
// Optimizations of path storage such as `PathTree` are not general enough to
// store k shortest paths: the set of paths for a given index for many
// sources/destinations is not ensured to form a set for each index. (While the
// first paths will form such a tree, storing *different* second paths for each
// source-destination pair may be impossible to do in a tree.)
//
// Unlike the functions in `shortest_paths.h`, the functions in this file
// directly return their result, to follow the current best practices.
#ifndef OR_TOOLS_GRAPH_K_SHORTEST_PATHS_H_
#define OR_TOOLS_GRAPH_K_SHORTEST_PATHS_H_
#include <algorithm>
#include <iterator>
#include <limits>
#include <queue>
#include <tuple>
#include <utility>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/base/optimization.h"
#include "absl/log/check.h"
#include "absl/types/span.h"
#include "ortools/graph/bounded_dijkstra.h"
#include "ortools/graph/ebert_graph.h"
#include "ortools/graph/shortest_paths.h"
namespace operations_research {
// Stores the solution to a k-shortest path problem. `paths` contains up to `k`
// paths from `source` to `destination` (these nodes are arguments to the
// algorithm), each having a distance stored in `distances`.
//
// The paths in `paths` start with `origin` and end at `destination`.
//
// If the computations are unsuccessful for any reason, the vectors are empty.
struct KShortestPaths {
// The paths are stored as vectors of nodes, like the other graph algorithms.
// TODO(user): what about vectors of arcs? That might be faster
// (potentially, add a function to transform it into a vector of nodes if the
// user really needs it). It would also have the nice benefit of removing the
// need for `distances` (compute it on the fly), with a reference to the graph
// and the costs.
std::vector<std::vector<NodeIndex>> paths;
std::vector<PathDistance> distances;
};
// Computes up to k shortest paths from the node `source` to the node
// `destination` in the given directed `graph`. The paths are guaranteed not to
// have loops.
//
// Hypotheses on input (which are not checked at runtime):
// - No multigraphs (more than one edge or a pair of nodes). The behavior is
// undefined otherwise.
// - The `arc_lengths` are supposed to be nonnegative. The behavior is
// undefined otherwise.
// TODO(user): relax to "no negative-weight cycles" (no Dijkstra).
// - The graphs might have loops.
//
// This function uses Yen's algorithm, which guarantees to find the first k
// shortest paths in O(k n (m + n log n)) for n nodes and m edges. This
// algorithm is an implementation of the idea of detours.
//
// Yen, Jin Y. "Finding the k Shortest Loopless Paths in a Network". Management
// Science. 17 (11): 712716, 1971.
// https://doi.org/10.1287%2Fmnsc.17.11.712
template <class GraphType>
KShortestPaths YenKShortestPaths(const GraphType& graph,
const std::vector<PathDistance>& arc_lengths,
NodeIndex source, NodeIndex destination,
unsigned k);
// End of the interface. Below is the implementation.
// TODO(user): introduce an enum to choose the algorithm. It's useless as
// long as this file only provides Yen.
namespace internal {
const PathDistance kMaxDistance = std::numeric_limits<PathDistance>::max() - 1;
const PathDistance kDisconnectedDistance =
std::numeric_limits<PathDistance>::max();
// Determines the arc index from a source to a destination.
//
// This operation requires iterating through the set of outgoing arcs from the
// source node, which might be expensive.
//
// In a multigraph, this function returns an index for one of the edges between
// the source and the destination.
template <class GraphType>
ArcIndex FindArcIndex(const GraphType& graph, const NodeIndex source,
const NodeIndex destination) {
const auto outgoing_arcs_iter = graph.OutgoingArcs(source);
const auto arc =
std::find_if(outgoing_arcs_iter.begin(), outgoing_arcs_iter.end(),
[&graph, destination](const ArcIndex arc) {
return graph.Head(arc) == destination;
});
return (arc != outgoing_arcs_iter.end()) ? *arc : GraphType::kNilArc;
}
// Determines the shortest path from the given source and destination, returns a
// tuple with the path (as a vector of node indices) and its cost.
template <class GraphType>
std::tuple<std::vector<NodeIndex>, PathDistance> ComputeShortestPath(
const GraphType& graph, const std::vector<PathDistance>& arc_lengths,
const NodeIndex source, const NodeIndex destination) {
BoundedDijkstraWrapper<GraphType, PathDistance> dijkstra(&graph,
&arc_lengths);
dijkstra.RunBoundedDijkstra(source, kMaxDistance);
const PathDistance path_length = dijkstra.distances()[destination];
if (path_length >= kMaxDistance) {
// There are shortest paths in this graph, just not from the source to this
// destination.
// This case only happens when some arcs have an infinite length (i.e.
// larger than `kMaxDistance`): `BoundedDijkstraWrapper::NodePathTo` fails
// to return a path, even empty.
return {{}, kDisconnectedDistance};
}
if (std::vector<NodeIndex> path = std::move(dijkstra.NodePathTo(destination));
!path.empty()) {
return {std::move(path), path_length};
} else {
return {{}, kDisconnectedDistance};
}
}
// Computes the total length of a path.
template <class GraphType>
PathDistance ComputePathLength(const GraphType& graph,
const absl::Span<const PathDistance> arc_lengths,
const absl::Span<const NodeIndex> path) {
PathDistance distance = 0;
for (NodeIndex i = 0; i < path.size() - 1; ++i) {
const ArcIndex arc = internal::FindArcIndex(graph, path[i], path[i + 1]);
DCHECK_NE(arc, GraphType::kNilArc);
distance += arc_lengths[arc];
}
return distance;
}
// Stores a path with a priority (typically, the distance), with a comparison
// operator that operates on the priority.
class PathWithPriority {
public:
PathWithPriority(PathDistance priority, std::vector<NodeIndex> path)
: path_(std::move(path)), priority_(priority) {}
bool operator<(const PathWithPriority& other) const {
return priority_ < other.priority_;
}
[[nodiscard]] const std::vector<NodeIndex>& path() const { return path_; }
[[nodiscard]] PathDistance priority() const { return priority_; }
private:
std::vector<NodeIndex> path_;
PathDistance priority_;
};
// Container adapter to be used with STL container adapters such as
// std::priority_queue. It gives access to the underlying container, which is a
// protected member in a standard STL container adapter.
template <class Container>
class UnderlyingContainerAdapter : public Container {
public:
typedef typename Container::container_type container_type;
// No mutable version of `container`, so that the user cannot change the data
// within the container: they might destroy the container's invariants.
[[nodiscard]] const container_type& container() const { return this->c; }
};
} // namespace internal
// TODO(user): Yen's algorithm can work with negative weights, but
// Dijkstra cannot.
//
// Yen, Jin Y. "Finding the k Shortest Loopless Paths in a Network". Management
// Science. 17 (11): 712716, 1971.
// https://doi.org/10.1287%2Fmnsc.17.11.712
//
// Yen's notations:
// - Source node: (1).
// - Destination node: (N).
// - Path from (1) to (j): (1) - (i) - ... - (j).
// - Cost for following the arc from (i) to (j), potentially negative: d_ij.
// - k-th shortest path: A^k == (1) - (2^k) - (3^k) - ... - (Q_k^k) - (N).
// - Deviation from A^k-1 at (i): A_i^k. This is the shortest path from (1) to
// (N) that is identical to A^k-1 from (1) to (i^k-1), then different from all
// the first k-1 shortest paths {A^1, A^2, ..., A^k-1}.
// - Root of A_i^k: R_i^k. This is the first subpath of A_i^k that coincides
// with A^k-1, i.e. A_i^k until i^k-1.
// - Spur of A_i^k: S_i^k. This is the last subpart of A_i^k with only one node
// coinciding with A_i^k, (i^k-1), i.e. A_i^k from i^k-1 onwards.
//
// Example graph, paths from A to H (more classical notations):
// C - D
// / / \
// A - B / G - H
// \ / /
// E - F
// Source node: A. Destination node: H.
// Three paths from A to H, say they are ordered from the cheapest to the most
// expensive:
// - 1st path: A - B - C - D - G - H
// - 2nd path: A - B - E - F - G - H
// - 3rd path: A - B - E - D - G - H
// To start with, Yen's algorithm uses the shortest path:
// A^1 = A - B - C - D - G - H
// To compute the second path A^2, compute a detour around A^1. Consider the
// iteration where B is the spur node.
// - Spur node: 2^1 = B.
// - Root of A^1_2: R_1^2 = A - B (including the spur node 2^1 = B).
// - Spur path S_1^2 starts at the spur node 2^1 = B. There are two possible
// spur paths, the cheapest being:
// S_1^2 = B - E - F - G - H
template <class GraphType>
KShortestPaths YenKShortestPaths(const GraphType& graph,
const std::vector<PathDistance>& arc_lengths,
NodeIndex source, NodeIndex destination,
unsigned k) {
CHECK_GT(internal::kDisconnectedDistance, internal::kMaxDistance);
CHECK_GE(k, 0) << "k must be nonnegative. Input value: " << k;
CHECK_NE(k, 0) << "k cannot be zero: you are requesting zero paths!";
CHECK_GT(graph.num_nodes(), 0) << "The graph is empty: it has no nodes";
CHECK_GT(graph.num_arcs(), 0) << "The graph is empty: it has no arcs";
CHECK_GE(source, 0) << "The source node must be nonnegative. Input value: "
<< source;
CHECK_LT(source, graph.num_nodes())
<< "The source node must be a valid node. Input value: " << source
<< ". Number of nodes in the input graph: " << graph.num_nodes();
CHECK_GE(destination, 0)
<< "The source node must be nonnegative. Input value: " << destination;
CHECK_LT(destination, graph.num_nodes())
<< "The destination node must be a valid node. Input value: "
<< destination
<< ". Number of nodes in the input graph: " << graph.num_nodes();
KShortestPaths paths;
// First step: compute the shortest path.
{
std::tuple<std::vector<NodeIndex>, PathDistance> first_path =
internal::ComputeShortestPath(graph, arc_lengths, source, destination);
if (std::get<0>(first_path).empty()) return paths;
paths.paths.push_back(std::move(std::get<0>(first_path)));
paths.distances.push_back(std::get<1>(first_path));
}
if (k == 1) {
return paths;
}
// Generate variant paths.
internal::UnderlyingContainerAdapter<
std::priority_queue<internal::PathWithPriority>>
variant_path_queue;
for (; k > 0; --k) {
// Generate variant paths from the last shortest path.
const absl::Span<NodeIndex> last_shortest_path =
absl::MakeSpan(paths.paths.back());
// TODO(user): think about adding parallelism for this loop to improve
// running times.
for (int spur_node_position = 0;
spur_node_position < last_shortest_path.size() - 1;
++spur_node_position) {
if (spur_node_position > 0) {
DCHECK_NE(last_shortest_path[spur_node_position], source);
}
DCHECK_NE(last_shortest_path[spur_node_position], destination);
const NodeIndex spur_node = last_shortest_path[spur_node_position];
// Consider the part of the last shortest path up to and excluding the
// spur node. If spur_node_position == 0, this span only contains the
// source node.
const absl::Span<NodeIndex> root_path =
last_shortest_path.subspan(0, spur_node_position + 1);
DCHECK_GE(root_path.length(), 1);
DCHECK_NE(root_path.back(), destination);
// Simplify the graph to have different paths using infinite lengths:
// copy the weights, set some of them to infinity. There is no need to
// restore the graph to its previous state in this case.
//
// This trick is used in the original article (it's old-fashioned), but
// not in Wikipedia's pseudocode (it prefers mutating the graph, which is
// harder to do without copying the whole graph structure).
// Copying the whole graph might be quite expensive, especially as it is
// not useful for long (computing one shortest path).
std::vector<PathDistance> arc_lengths_for_detour = arc_lengths;
for (absl::Span<const NodeIndex> previous_path : paths.paths) {
// Check among the previous paths: if part of the path coincides with
// the first few nodes up to the spur node (included), forbid this part
// of the path in the search for the next shortest path. More
// precisely, in that case, avoid the arc from the spur node to the
// next node in the path.
if (previous_path.size() < spur_node_position) continue;
const bool has_same_prefix_as_root_path = std::equal(
root_path.begin(), root_path.end(), previous_path.begin(),
previous_path.begin() + root_path.length());
if (has_same_prefix_as_root_path) {
const ArcIndex after_spur_node_arc =
internal::FindArcIndex(graph, previous_path[spur_node_position],
previous_path[spur_node_position + 1]);
arc_lengths_for_detour[after_spur_node_arc] =
internal::kDisconnectedDistance;
}
}
// Generate a new candidate path from the spur node to the destination
// without using the forbidden arcs.
{
std::tuple<std::vector<NodeIndex>, PathDistance> detour_path =
internal::ComputeShortestPath(graph, arc_lengths_for_detour,
spur_node, destination);
if (std::get<0>(detour_path).empty()) {
// Node unreachable after some arcs are forbidden.
continue;
}
std::vector<NodeIndex> spur_path = std::move(std::get<0>(detour_path));
if (ABSL_PREDICT_FALSE(spur_path.empty())) continue;
#ifndef NDEBUG
CHECK_EQ(root_path.back(), spur_path.front());
if (spur_path.size() == 1) {
CHECK_EQ(spur_path.front(), destination);
} else {
// Ensure there is an edge between the end of the root path
// and the beginning of the spur path (knowing that both subpaths
// coincide at the spur node).
const bool root_path_leads_to_spur_path = absl::c_any_of(
graph.OutgoingArcs(root_path.back()),
[&graph, node_after_spur_in_spur_path =
*(spur_path.begin() + 1)](const ArcIndex arc_index) {
return graph.Head(arc_index) == node_after_spur_in_spur_path;
});
CHECK(root_path_leads_to_spur_path);
}
#endif // !defined(NDEBUG)
// Assemble the new path.
std::vector<NodeIndex> new_path;
absl::c_copy(root_path.subspan(0, spur_node_position),
std::back_inserter(new_path));
absl::c_copy(spur_path, std::back_inserter(new_path));
DCHECK_EQ(new_path.front(), source);
DCHECK_EQ(new_path.back(), destination);
// Ensure the new path is not one of the previously known ones. This
// operation is required, as there are two sources of paths from the
// source to the destination:
// - `paths`, the list of paths that is output by the function: there
// is no possible duplicate due to `arc_lengths_for_detour`, where
// edges that might generate a duplicate path are forbidden.
// - `variant_path_queue`, the list of potential paths, ordered by
// their cost, with no impact on `arc_lengths_for_detour`.
// TODO(user): would it be faster to fingerprint the paths and
// filter by fingerprints? Due to the probability of error with
// fingerprints, still use this slow-but-exact code, but after
// filtering.
const bool is_new_path_already_known =
std::any_of(variant_path_queue.container().cbegin(),
variant_path_queue.container().cend(),
[&new_path](const internal::PathWithPriority& element) {
return element.path() == new_path;
});
if (is_new_path_already_known) continue;
const PathDistance path_length =
internal::ComputePathLength(graph, arc_lengths, new_path);
variant_path_queue.emplace(
/*priority=*/path_length, /*path=*/std::move(new_path));
}
}
// Add the shortest spur path ever found that has not yet been added. This
// can be a spur path that has just been generated or a previous one, if
// this iteration found no shorter one.
if (variant_path_queue.empty()) break;
const internal::PathWithPriority& next_shortest_path =
variant_path_queue.top();
paths.paths.emplace_back(next_shortest_path.path());
paths.distances.push_back(next_shortest_path.priority());
variant_path_queue.pop();
}
return paths;
}
} // namespace operations_research
#endif // OR_TOOLS_GRAPH_K_SHORTEST_PATHS_H_

View File

@@ -0,0 +1,171 @@
// 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/k_shortest_paths.h"
#include <vector>
#include "gtest/gtest.h"
#include "ortools/base/gmock.h"
#include "ortools/graph/graph.h"
#include "ortools/graph/shortest_paths.h"
namespace operations_research {
namespace {
using testing::ElementsAre;
using util::StaticGraph;
TEST(KShortestPathsYenDeathTest, EmptyGraph) {
StaticGraph<> graph;
std::vector<PathDistance> lengths;
EXPECT_DEATH(YenKShortestPaths(graph, lengths, /*source=*/0,
/*destination=*/1, /*k=*/10),
"graph.num_nodes\\(\\) > 0");
}
TEST(KShortestPathsYenDeathTest, NoArcGraph) {
StaticGraph<> graph;
graph.AddNode(1);
(void)graph.Build();
std::vector<PathDistance> lengths;
EXPECT_DEATH(YenKShortestPaths(graph, lengths, /*source=*/0,
/*destination=*/1, /*k=*/10),
"graph.num_arcs\\(\\) > 0");
}
TEST(KShortestPathsYenDeathTest, NonExistingSourceBecauseNegative) {
StaticGraph<> graph;
graph.AddNode(1);
graph.AddArc(0, 1);
(void)graph.Build();
std::vector<PathDistance> lengths{0};
EXPECT_DEATH(YenKShortestPaths(graph, lengths, /*source=*/-1,
/*destination=*/1, /*k=*/10),
"source >= 0");
}
TEST(KShortestPathsYenDeathTest, NonExistingSourceBecauseTooLarge) {
StaticGraph<> graph;
graph.AddNode(1);
graph.AddArc(0, 1);
(void)graph.Build();
std::vector<PathDistance> lengths{0};
EXPECT_DEATH(YenKShortestPaths(graph, lengths, /*source=*/1'000,
/*destination=*/1, /*k=*/10),
"source < graph.num_nodes()");
}
TEST(KShortestPathsYenDeathTest, NonExistingDestinationBecauseNegative) {
StaticGraph<> graph;
graph.AddNode(1);
graph.AddArc(0, 1);
(void)graph.Build();
std::vector<PathDistance> lengths{0};
EXPECT_DEATH(YenKShortestPaths(graph, lengths, /*source=*/0,
/*destination=*/-1, /*k=*/10),
"destination >= 0");
}
TEST(KShortestPathsYenDeathTest, NonExistingDestinationBecauseTooLarge) {
StaticGraph<> graph;
graph.AddNode(1);
graph.AddArc(0, 1);
(void)graph.Build();
std::vector<PathDistance> lengths{0};
EXPECT_DEATH(YenKShortestPaths(graph, lengths, /*source=*/0,
/*destination=*/1'000, /*k=*/10),
"destination < graph.num_nodes()");
}
TEST(KShortestPathsYenDeathTest, KEqualsZero) {
StaticGraph<> graph;
graph.AddArc(0, 1);
graph.AddArc(1, 2);
(void)graph.Build();
std::vector<PathDistance> lengths{1, 1};
EXPECT_DEATH(YenKShortestPaths(graph, lengths, /*source=*/0,
/*destination=*/2, /*k=*/0),
"k != 0");
}
TEST(KShortestPathsYenTest, ReducesToShortestPath) {
StaticGraph<> graph;
graph.AddArc(0, 1);
graph.AddArc(1, 2);
(void)graph.Build();
std::vector<PathDistance> lengths{1, 1};
const KShortestPaths paths = YenKShortestPaths(graph, lengths, /*source=*/0,
/*destination=*/2, /*k=*/1);
EXPECT_THAT(paths.paths, ElementsAre(std::vector<int>{0, 1, 2}));
EXPECT_THAT(paths.distances, ElementsAre(2));
}
TEST(KShortestPathsYenTest, OnlyHasOnePath) {
StaticGraph<> graph;
graph.AddArc(0, 1);
graph.AddArc(1, 2);
(void)graph.Build();
std::vector<PathDistance> lengths{1, 1};
const KShortestPaths paths = YenKShortestPaths(graph, lengths, /*source=*/0,
/*destination=*/2, /*k=*/10);
EXPECT_THAT(paths.paths, ElementsAre(std::vector<int>{0, 1, 2}));
EXPECT_THAT(paths.distances, ElementsAre(2));
}
TEST(KShortestPathsYenTest, HasTwoPaths) {
StaticGraph<> graph;
graph.AddArc(0, 1);
graph.AddArc(0, 2);
graph.AddArc(1, 2);
(void)graph.Build();
std::vector<PathDistance> lengths{1, 30, 1};
const KShortestPaths paths = YenKShortestPaths(graph, lengths, /*source=*/0,
/*destination=*/2, /*k=*/10);
EXPECT_THAT(paths.paths,
ElementsAre(std::vector<int>{0, 1, 2}, std::vector<int>{0, 2}));
EXPECT_THAT(paths.distances, ElementsAre(2, 30));
}
TEST(KShortestPathsYenTest, HasTwoPathsWithLongerPath) {
StaticGraph<> graph;
graph.AddArc(0, 1);
graph.AddArc(0, 4);
graph.AddArc(1, 2);
graph.AddArc(2, 3);
graph.AddArc(3, 4);
(void)graph.Build();
std::vector<PathDistance> lengths{1, 30, 1, 1, 1};
const KShortestPaths paths = YenKShortestPaths(graph, lengths, /*source=*/0,
/*destination=*/4, /*k=*/10);
EXPECT_THAT(paths.paths, ElementsAre(std::vector<int>{0, 1, 2, 3, 4},
std::vector<int>{0, 4}));
EXPECT_THAT(paths.distances, ElementsAre(4, 30));
}
// TODO(user): randomized tests? Check validity with exhaustive
// exploration/IP formulation?
} // namespace
} // namespace operations_research

View File

@@ -19,6 +19,7 @@
#include <vector>
#include "absl/random/distributions.h"
#include "absl/types/span.h"
#include "benchmark/benchmark.h"
#include "gtest/gtest.h"
#include "ortools/base/commandlineflags.h"
@@ -743,7 +744,7 @@ class ReorderedGraphTest : public testing::Test {
ReorderedGraphTest() {}
void TestMe(const size_t left_nodes, const std::vector<Edge>& ordered_edges) {
void TestMe(const size_t left_nodes, absl::Span<const Edge> ordered_edges) {
std::vector<int64_t> edge_costs;
typedef util::StaticGraph<size_t, size_t> GraphType;
GraphType graph(2 * left_nodes, ordered_edges.size());

View File

@@ -191,8 +191,8 @@ TEST(SimpleMaxFlowTest, ProblematicProblemWithMaxCapacity) {
FlowModelProto model,
ReadFileToProto<FlowModelProto>(
file::JoinPathRespectAbsolute(absl::GetFlag(FLAGS_test_srcdir),
"ortools/graph/"
"testdata/max_flow_test1.pb.txt")));
"ortools/graph/"
"testdata/max_flow_test1.pb.txt")));
SimpleMaxFlow solver;
EXPECT_EQ(SimpleMaxFlow::OPTIMAL, LoadAndSolveFlowModel(model, &solver));
EXPECT_EQ(10290243, solver.OptimalFlow());

View File

@@ -78,10 +78,10 @@ void CheckMSTWithKruskal(const ListGraph<int, int>& graph,
// Helper function to check the expected MST is obtained with Prim.
void CheckMSTWithPrim(const ListGraph<int, int>& graph,
const std::vector<int64_t>& costs,
absl::Span<const int64_t> costs,
const std::vector<int>& expected_arcs) {
const std::vector<int> prim_mst = BuildPrimMinimumSpanningTree(
graph, [&costs](int arc) { return costs[arc]; });
graph, [costs](int arc) { return costs[arc]; });
EXPECT_THAT(expected_arcs, UnorderedElementsAreArray(prim_mst));
}

View File

@@ -335,10 +335,10 @@ int GetNodeMinimizingEdgeCostToSource(const GraphType& graph, int source,
template <typename CostFunction, typename GraphType, typename CostType>
std::vector<int> ComputeOneTree(const GraphType& graph,
const CostFunction& cost,
const std::vector<double>& weights,
const std::vector<int>& sorted_arcs,
absl::Span<const double> weights,
absl::Span<const int> sorted_arcs,
CostType* one_tree_cost) {
const auto weighed_cost = [&cost, &weights](int from, int to) {
const auto weighed_cost = [&cost, weights](int from, int to) {
return cost(from, to) + weights[from] + weights[to];
};
// Compute MST on graph.

View File

@@ -23,7 +23,7 @@
#include "gtest/gtest.h"
#include "ortools/base/path.h"
#include "ortools/base/types.h"
#include "ortools/routing/tsplib_parser.h"
#include "ortools/routing/parsers/tsplib_parser.h"
namespace operations_research {
namespace {

View File

@@ -248,7 +248,7 @@ std::vector<Edge> GenerateAndLoadRandomProblem(
// condition if really needed. This is a bit involved though, and with the MIP
// tests below, we should have a good enough confidence in the code already.
void CheckOptimalSolution(const MinCostPerfectMatching& matcher,
const std::vector<Edge>& edges) {
absl::Span<const Edge> edges) {
const std::vector<int>& matches = matcher.Matches();
std::vector<bool> seen(matches.size(), false);
int num_seen = 0;

View File

@@ -0,0 +1,173 @@
// 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/random_graph.h"
#include <algorithm>
#include <cstdint>
#include <memory>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_set.h"
#include "absl/memory/memory.h"
#include "absl/random/bit_gen_ref.h"
#include "absl/random/random.h"
#include "ortools/base/logging.h"
#include "ortools/base/types.h"
namespace util {
namespace {
// This function initializes the graph used by GenerateRandomMultiGraph() and
// GenerateRandomMultiGraph(), given their arguments.
// See the .h for documentation on those arguments.
std::unique_ptr<StaticGraph<>> CreateGraphMaybeReserved(int num_nodes,
int num_arcs,
bool finalized,
absl::BitGenRef gen) {
std::unique_ptr<StaticGraph<>> graph;
// We either "reserve" the number of nodes and arcs or not, depending on
// randomness and on the "finalized" bit (if false, we can't assume that the
// user won't add some nodes or arcs after we return the graph, so we can't
// cap those).
if (finalized && absl::Bernoulli(gen, 1.0 / 2)) {
graph = std::make_unique<StaticGraph<>>(num_nodes, num_arcs);
} else {
graph = std::make_unique<StaticGraph<>>();
graph->AddNode(num_nodes - 1);
}
return graph;
}
} // namespace
std::unique_ptr<StaticGraph<>> GenerateRandomMultiGraph(int num_nodes,
int num_arcs,
bool finalized,
absl::BitGenRef gen) {
std::unique_ptr<StaticGraph<>> graph =
CreateGraphMaybeReserved(num_nodes, num_arcs, finalized, gen);
if (num_nodes != 0) {
CHECK_GT(num_nodes, 0);
CHECK_GE(num_arcs, 0);
graph->AddNode(num_nodes - 1);
for (int a = 0; a < num_arcs; ++a) {
graph->AddArc(absl::Uniform(gen, 0, num_nodes),
absl::Uniform(gen, 0, num_nodes));
}
} else {
CHECK_EQ(num_arcs, 0);
}
if (finalized) graph->Build();
return graph;
}
namespace {
// Parameterized method to generate both directed and undirected simple graphs.
std::unique_ptr<StaticGraph<>> GenerateRandomSimpleGraph(int num_nodes,
int num_arcs,
bool finalized,
bool directed,
absl::BitGenRef gen) {
CHECK_GE(num_nodes, 0);
// For an undirected graph, the number of arcs should be even: a->b and b->a.
CHECK(directed || (num_arcs % 2 == 0));
const int64_t max_num_arcs =
static_cast<int64_t>(num_nodes) * (num_nodes - 1);
CHECK_LE(num_arcs, max_num_arcs);
std::unique_ptr<StaticGraph<>> graph =
CreateGraphMaybeReserved(num_nodes, num_arcs, finalized, gen);
// If the number of arcs is greater than half the possible arcs of the graph,
// we generate the inverse graph and convert non-arcs to arcs.
if (num_arcs > max_num_arcs / 2) {
std::unique_ptr<StaticGraph<>> inverse_graph =
GenerateRandomSimpleGraph(num_nodes, max_num_arcs - num_arcs,
/*finalized=*/true, directed, gen);
std::vector<bool> node_mask(num_nodes, false);
for (int from = 0; from < num_nodes; ++from) {
for (const int to : (*inverse_graph)[from]) {
node_mask[to] = true;
}
for (int to = 0; to < num_nodes; ++to) {
if (node_mask[to]) {
node_mask[to] = false; // So that the mask is reset to all false.
} else if (to != from) {
graph->AddArc(from, to);
}
}
}
if (finalized) graph->Build();
return graph;
}
// We use a trivial algorithm: pick an arc at random, uniformly, and add it to
// the graph unless it was already added. As we sometimes have to discard an
// arc, we expect to do this slightly more times than the desired number "m"
// of distinct arcs. But in the worst case, which is when m = M/2 (where M =
// N*(N-1) is the number of possible arcs), the expected number of steps is
// only ln(2)*M ~ 0.69*M, to produce 0.5*M arcs. So it's fine.
//
// Proof: The expected number of steps to get "m" distinct arcs across the M
// possible arcs is M/M + M/(M-1) + M/(M-2) + ... + M/(M-m+1), which is equal
// to M * (H(M) - H(M-m)), where H(x) is the harmonic sum up to x.
// H(M) - H(M-m) converges to ln(M) - ln(M-m) = ln(1 + m/(M-m)) as M grows,
// which stricly grows with m and is equal to ln(2) in the worst case m=M/2.
//
// NOTE(user): If some specialized users want a uniform generation method
// that uses less memory (this one uses a flat hash map on the arcs, which
// uses significant memory), it could be done. Reach out to me.
absl::flat_hash_set<std::pair<int, int>> arc_set;
// To detect bad user-provided random number generator which could lead to
// infinite loops, we bound the number of iterations to a value well beyond
// the expected number of iterations (which is less than 0.69 * max_num_arcs).
int64_t num_iterations = 0;
const int64_t max_num_iterations = 1000 + max_num_arcs;
while (graph->num_arcs() < num_arcs) {
++num_iterations;
CHECK_LE(num_iterations, max_num_iterations)
<< "The random number generator supplied to GenerateRandomSimpleGraph()"
<< " is likely biased or broken.";
const int tail = absl::Uniform(gen, 0, num_nodes);
const int head = absl::Uniform(gen, 0, num_nodes);
if (tail == head) continue;
if (directed) {
if (!arc_set.insert({tail, head}).second) continue;
graph->AddArc(tail, head);
} else { // undirected
const std::pair<int, int> arc = {
std::min(tail, head),
std::max(tail, head)}; // Canonic edge representative.
if (!arc_set.insert(arc).second) continue;
graph->AddArc(tail, head);
graph->AddArc(head, tail);
}
}
if (finalized) graph->Build();
return graph;
}
} // namespace
std::unique_ptr<StaticGraph<>> GenerateRandomDirectedSimpleGraph(
int num_nodes, int num_arcs, bool finalized, absl::BitGenRef gen) {
return GenerateRandomSimpleGraph(num_nodes, num_arcs, finalized,
/*directed=*/true, gen);
}
std::unique_ptr<StaticGraph<>> GenerateRandomUndirectedSimpleGraph(
int num_nodes, int num_edges, bool finalized, absl::BitGenRef gen) {
return GenerateRandomSimpleGraph(num_nodes, 2 * num_edges, finalized,
/*directed=*/false, gen);
}
} // namespace util

View File

@@ -0,0 +1,51 @@
// 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.
// A collection of functions to be used in unit tests involving the
// ortools/graph/... library.
#ifndef UTIL_GRAPH_RANDOM_GRAPH_H_
#define UTIL_GRAPH_RANDOM_GRAPH_H_
#include <memory>
#include "absl/random/bit_gen_ref.h"
#include "ortools/graph/graph.h"
namespace util {
// Generates a random graph where multi-arcs and self-arcs are allowed (and
// therefore expected): exactly "num_arcs" are generated, each from a node
// picked uniformly at random to another node picked uniformly at random.
// Calls Build() on the graph iff "finalized" is true.
std::unique_ptr<StaticGraph<>> GenerateRandomMultiGraph(int num_nodes,
int num_arcs,
bool finalized,
absl::BitGenRef gen);
// Like GenerateRandomMultiGraph(), but with neither multi-arcs nor self-arcs:
// the generated graph will have exactly num_arcs arcs. It will be picked
// uniformly at random from the set of all simple graphs with that number of
// nodes and arcs.
std::unique_ptr<StaticGraph<>> GenerateRandomDirectedSimpleGraph(
int num_nodes, int num_arcs, bool finalized, absl::BitGenRef gen);
// Like GenerateRandomDirectedSimpleGraph(), but where an undirected edge is
// represented by two arcs: a->b and b->a. As a result, the amount of arcs in
// the generated graph is 2*num_edges.
std::unique_ptr<StaticGraph<>> GenerateRandomUndirectedSimpleGraph(
int num_nodes, int num_edges, bool finalized, absl::BitGenRef gen);
} // namespace util
#endif // UTIL_GRAPH_RANDOM_GRAPH_H_

View File

@@ -21,6 +21,7 @@
#include "absl/container/flat_hash_map.h"
#include "absl/functional/bind_front.h"
#include "absl/log/check.h"
#include "absl/types/span.h"
#include "ortools/base/adjustable_priority_queue-inl.h"
#include "ortools/base/adjustable_priority_queue.h"
#include "ortools/base/logging.h"
@@ -105,8 +106,8 @@ class PathTree {
public:
PathTree() : nodes_(), parents_() {}
void Initialize(const std::vector<NodeIndex>& paths,
const std::vector<NodeIndex>& destinations);
void Initialize(absl::Span<const NodeIndex> paths,
absl::Span<const NodeIndex> destinations);
// Returns the parent (predecessor) of 'node' in the tree in
// O(log(path_tree_size)), where path_tree_size is the size of nodes_.
@@ -126,8 +127,8 @@ class PathTree {
// Initializes the tree from a non-sparse representation of the path tree
// represented by 'paths'. The tree is reduced to the subtree in which nodes in
// 'destinations' are the leafs.
void PathTree::Initialize(const std::vector<NodeIndex>& paths,
const std::vector<NodeIndex>& destinations) {
void PathTree::Initialize(absl::Span<const NodeIndex> paths,
absl::Span<const NodeIndex> destinations) {
const NodeIndex kNilNode = StarGraph::kNilNode;
std::vector<bool> node_explored(paths.size(), false);
const int destination_size = destinations.size();
@@ -256,7 +257,7 @@ class DistanceContainer : public PathContainerImpl {
std::vector<int> reverse_destinations_;
private:
static void ComputeReverse(const std::vector<NodeIndex>& nodes,
static void ComputeReverse(absl::Span<const NodeIndex> nodes,
NodeIndex num_nodes,
std::vector<int>* reverse_nodes) {
CHECK(reverse_nodes != nullptr);

View File

@@ -12,5 +12,5 @@
# limitations under the License.
exports_files([
"max_flow_test1.pb.txt",
"max_flow_test1.pb.txt",
])

View File

@@ -72,7 +72,7 @@ std::unique_ptr<Graph> CopyGraph(const Graph& graph);
// Note that you can call IsValidPermutation() to check it yourself.
template <class Graph>
std::unique_ptr<Graph> RemapGraph(const Graph& graph,
const std::vector<int>& new_node_index);
absl::Span<const int> new_node_index);
// Gets the induced subgraph of "graph" restricted to the nodes in "nodes":
// the resulting graph will have exactly nodes.size() nodes, and its
@@ -277,7 +277,7 @@ std::unique_ptr<Graph> CopyGraph(const Graph& graph) {
template <class Graph>
std::unique_ptr<Graph> RemapGraph(const Graph& old_graph,
const std::vector<int>& new_node_index) {
absl::Span<const int> new_node_index) {
DCHECK(IsValidPermutation(new_node_index)) << "Invalid permutation";
const int num_nodes = old_graph.num_nodes();
CHECK_EQ(new_node_index.size(), num_nodes);