Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
bfs.h
Go to the documentation of this file.
1// Copyright 2010-2024 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14#ifndef UTIL_GRAPH_BFS_H_
15#define UTIL_GRAPH_BFS_H_
16
17#include <cstddef>
18#include <limits>
19#include <vector>
20
21#include "absl/status/status.h"
22#include "absl/status/statusor.h"
23#include "absl/strings/str_format.h"
24
25// These 3 functions give the full functionality of a BFS (Breadth-First-Search)
26// on any type of Graph on dense integers that implements the [] operator to
27// yield the adjacency list: graph[i] should yield a vector<int>-like object
28// that lists all the (outgoing) neighbors of node #i.
29//
30// If your graph is undirected, it means that for each edge (i,j), graph[i] must
31// contain j and graph[j] must contain i.
32//
33// Self-arcs and multi-arcs are supported, since they don't affect BFS.
34//
35// These functions are fast: they have the optimal asymptotic complexity, and
36// are reasonably optimized. You may still get performance gains if you
37// implement your own BFS for your application. In particular, this API is
38// optimized for repeated calls to GetBFSShortestPath(); if you only care about
39// GetBFSDistances() there exists more optimized implementations.
40//
41// ERRORS:
42// This library does perform many checks at runtime, and returns an error Status
43// if it detects a problem, but it doesn't fully protect you from crashes if the
44// input is ill-formed in some ways this library can't check. For example, if
45// calling graph[i] crashes for some i in 0..num_nodes-1, this won't detect it.
46//
47// Example:
48// const int num_nodes = 3;
49// vector<vector<int>> graph(num_nodes) = {{1}, {0}, {1, 2}}; // 0↔1←2⟲
50// const int source = 1;
51// vector<int> bfs_tree = GetBFSRootedTree(graph, num_nodes, source).value();
52// LOG(INFO) << DUMP_VARS(GetBFSDistances(bfs_tree));
53// for (int target : {0, 1, 2}) {
54// vector<int> path = GetBFSShortestPath(bfs_tree, target);
55// LOG(INFO) << DUMP_VARS(path);
56// }
57//
58// Would log this: GetBFSDistances(bfs_tree) = [1, 0, -1]
59// path = [1, 0]
60// path = [1]
61// path = [] // no path from 1 to 2.
62
63namespace util {
64namespace graph {
65
66// Runs a BFS (Breadth First Search) in O(num_nodes + num_arcs), and returns the
67// BFS tree rooted at the source: returned element #i is either:
68// - the parent of node #i, i.e. the node that precedes it in the shortest path
69// from the source to i;
70// - or -1, if the node wasn't reached;
71// - or itself, i.e. i, if #i is the source.
72//
73// TIE BREAKING: This implementation always breaks tie the same way, by order
74// in the adjacency lists. E.g. if all the adjacency lists are sorted, then the
75// parent of a node in the BFS tree is always the smalled possible parent.
76template <class Graph, class NodeIndex = int>
77absl::StatusOr<std::vector<NodeIndex>> GetBFSRootedTree(const Graph& graph,
78 NodeIndex num_nodes,
79 NodeIndex source);
80
81// Returns the distances of all nodes from the source, in O(num_nodes).
82// `bfs_tree` must be exactly as returned by GetBFSRootedTree().
83// NOTE: Supports BFS forests, i.e. the result of a BFS from multiple sources.
84template <class NodeIndex>
85absl::StatusOr<std::vector<NodeIndex>> GetBFSDistances(
86 const std::vector<NodeIndex>& bfs_tree);
87
88// Returns the shortest path from the source to `target`, in O(path length).
89// `bfs_tree` must be exactly as returned by GetBFSRootedTree().
90// If `target` wasn't reached in the BFS, returns the empty vector. Else the
91// returned path always starts with the source and ends with the target (if
92// source=target, returns [source]).
93template <class NodeIndex>
94absl::StatusOr<std::vector<NodeIndex>> GetBFSShortestPath(
95 const std::vector<NodeIndex>& bfs_tree, NodeIndex target);
96
97// _____________________________________________________________________________
98// Implementation of the templates.
99
100template <class Graph, class NodeIndex>
101absl::StatusOr<std::vector<NodeIndex>> GetBFSRootedTree(const Graph& graph,
102 NodeIndex num_nodes,
103 NodeIndex source) {
104 if (source < 0 || source >= num_nodes) {
105 return absl::InvalidArgumentError(
106 absl::StrFormat("Invalid graph: source=%d is not in [0, num_nodes=%d)",
107 source, num_nodes));
108 }
109 // NOTE(user): -1 works fine for unsigned integers because 'num_nodes' is
110 // already an exclusive upper bound for node indices (and -1 unsigned can only
111 // be ≥ num_nodes).
112 constexpr NodeIndex kNone = -1; // NOLINT
113 std::vector<NodeIndex> bfs_tree(num_nodes, kNone);
114 bfs_tree[source] = source;
115 std::vector<NodeIndex> bfs_queue = {source};
116 size_t num_visited = 0;
117 while (num_visited < bfs_queue.size()) {
118 const NodeIndex node = bfs_queue[num_visited++];
119 for (const NodeIndex child : graph[node]) {
120 if (child < 0 || child >= num_nodes) {
121 return absl::InvalidArgumentError(
122 absl::StrFormat("Invalid graph: graph[%d] contains %d, which is "
123 "not a valid node index in [0, num_nodes=%d)",
124 node, child, num_nodes));
125 }
126 if (bfs_tree[child] != kNone) continue; // Already visited.
127 bfs_tree[child] = node;
128 bfs_queue.push_back(child);
129 }
130 }
131 return bfs_tree;
132}
133
134template <class NodeIndex>
135absl::StatusOr<std::vector<NodeIndex>> GetBFSDistances(
136 const std::vector<NodeIndex>& bfs_tree) {
137 const NodeIndex n = bfs_tree.size();
138 constexpr NodeIndex kNone = -1; // NOLINT
139
140 // Run a few checks on the input first.
141 constexpr NodeIndex kMaxNodeIndex = std::numeric_limits<NodeIndex>::max();
142 if (bfs_tree.size() > kMaxNodeIndex) {
143 return absl::InvalidArgumentError(absl::StrFormat(
144 "bfs_tree.size()=%d is too large for its integer node type (max=%d)",
145 bfs_tree.size(), kMaxNodeIndex));
146 }
147 for (NodeIndex i = 0; i < n; ++i) {
148 const NodeIndex parent = bfs_tree[i];
149 if (parent != kNone && (parent < 0 || parent >= n)) {
150 return absl::InvalidArgumentError(absl::StrFormat(
151 "bfs_tree[%d]=%d is outside [0, bfs_tree.size()=%d)", i, parent, n));
152 }
153 }
154
155 // The algorithm starts.
156 std::vector<NodeIndex> distance(n, kNone);
157 for (NodeIndex i = 0; i < n; ++i) {
158 if (bfs_tree[i] == kNone) continue;
159 // Ascend the parent tree until we reach either the root (the BFS source),
160 // or an already-marked node (whose distance we already know).
161 NodeIndex p = i;
162 NodeIndex dist = 0;
163 while (bfs_tree[p] != p && distance[p] == kNone) {
164 p = bfs_tree[p];
165 ++dist;
166 if (dist >= n) {
167 return absl::InvalidArgumentError(
168 absl::StrFormat("bfs_tree isn't a BFS tree: detected a cycle in"
169 " the ascendance of node %d",
170 i));
171 }
172 if (p == kNone) {
173 return absl::InvalidArgumentError(
174 absl::StrFormat("bfs_tree isn't a BFS tree: detected an"
175 " interrupted ascendance from %d",
176 i));
177 }
178 }
179 // We've reached the root or an already-marked node. Update our distance.
180 if (bfs_tree[p] == p) distance[p] = 0;
181 dist += distance[p];
182 // Now ascend the path a second time, to mark the distances of all nodes on
183 // the path.
184 const NodeIndex known_node = p;
185 p = i;
186 while (p != known_node) {
187 distance[p] = dist;
188 --dist;
189 p = bfs_tree[p];
190 }
191 }
192 return distance;
193}
194
195template <class NodeIndex>
196absl::StatusOr<std::vector<NodeIndex>> GetBFSShortestPath(
197 const std::vector<NodeIndex>& bfs_tree, NodeIndex target) {
198 const NodeIndex n = bfs_tree.size();
199 if (target < 0 || target >= n) {
200 return absl::InvalidArgumentError(absl::StrFormat(
201 "target=%d is outside [0, bfs_tree.size()=%d)", target, n));
202 }
203
204 std::vector<NodeIndex> path;
205 constexpr NodeIndex kNone = -1; // NOLINT
206 if (bfs_tree[target] == kNone) return path;
207 while (true) {
208 if (path.size() >= bfs_tree.size()) {
209 return absl::InvalidArgumentError(
210 absl::StrFormat("bfs_tree isn't a BFS tree: detected a cycle in"
211 " the ascendance of node %d",
212 target));
213 }
214 path.push_back(target);
215 const NodeIndex new_target = bfs_tree[target];
216 if (new_target == target) break;
217 if (new_target < 0 || new_target >= n) {
218 return absl::InvalidArgumentError(
219 absl::StrFormat("bfs_tree[%d]=%d is outside [0, bfs_tree.size()=%d)",
220 target, new_target, n));
221 }
222 target = new_target;
223 }
224 std::reverse(path.begin(), path.end());
225 return path;
226}
227
228} // namespace graph
229} // namespace util
230
231#endif // UTIL_GRAPH_BFS_H_
GraphType graph
absl::StatusOr< std::vector< NodeIndex > > GetBFSShortestPath(const std::vector< NodeIndex > &bfs_tree, NodeIndex target)
Definition bfs.h:196
absl::StatusOr< std::vector< NodeIndex > > GetBFSRootedTree(const Graph &graph, NodeIndex num_nodes, NodeIndex source)
Definition bfs.h:101
absl::StatusOr< std::vector< NodeIndex > > GetBFSDistances(const std::vector< NodeIndex > &bfs_tree)
Definition bfs.h:135
A collections of i/o utilities for the Graph classes in ./graph.h.
static const int kNone
Special value for task indices meaning 'no such task'.
Definition resource.cc:236
double distance