Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
adjustable_k_ary_heap.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 OR_TOOLS_ALGORITHMS_ADJUSTABLE_K_ARY_HEAP_H_
15#define OR_TOOLS_ALGORITHMS_ADJUSTABLE_K_ARY_HEAP_H_
16
17#include <algorithm>
18#include <limits>
19#include <utility>
20#include <vector>
21
22#include "absl/log/check.h"
23
24// Adjustable k-ary heap for std::pair<Priority, Index> classes containing a
25// priority and an index referring to an array where the relevant data is
26// stored.
27//
28// The comparator is the default comparator for pairs, i.e. the index is used as
29// a tie-breaker for the priority, thus making the code more repeatable.
30//
31// Because the class uses indices and vectors, it is much faster than
32// AdjustablePriorityQueue, even in the binary heap case.
33//
34// k-ary heaps are useful when SiftDown() (aka Decrease) is called more often
35// than Pop() (aka Extract).
36//
37// Namely, Pop() has a complexity in O(k * log_k (n)), while SiftDown() is in
38// O(log_k(n)), even when k = 2. This explains the small gain.
39//
40// In the implementation below, k is denoted as Arity.
41
42template <typename Priority, typename Index, int Arity, bool IsMaxHeap>
44 public:
45 using Aggregate = std::pair<Priority, Index>;
46 using HeapIndex = Index;
47 static_assert(Arity >= 2, "arity must be at least 2");
48 static_assert(std::numeric_limits<Index>::is_integer,
49 "Index must be an integer");
50 static_assert(std::numeric_limits<Priority>::is_specialized,
51 "Priority must be an integer or floating-point type");
53
54 // Construct a k-heap from an existing vector, tracking original indices.
55 // `universe_size` is the maximum possible index in `elements`.
56 explicit AdjustableKAryHeap(const std::vector<Aggregate>& elements,
57 HeapIndex universe_size) {
58 Load(elements, universe_size);
59 }
60
61 explicit AdjustableKAryHeap(const std::vector<Index>& indices,
62 const std::vector<Priority>& priorities,
63 HeapIndex universe_size) {
64 Load(indices, priorities, universe_size);
65 }
66
67 void Clear() {
68 data_.clear();
69 heap_positions_.clear();
70 heap_size_ = 0;
71 }
72
73 void Load(const std::vector<Aggregate>& elements, HeapIndex universe_size) {
74 data_.resize(elements.size());
75 heap_size_ = elements.size();
76 std::copy(elements.begin(), elements.end(), data_.begin());
77 heap_positions_.resize(universe_size, kNonExistent);
78 for (HeapIndex i = 0; i < data_.size(); ++i) {
79 heap_positions_[index(i)] = i;
80 }
81 BuildHeap();
82 }
83
84 void Load(const std::vector<Index>& indices,
85 const std::vector<Priority>& priorities, HeapIndex universe_size) {
86 std::copy(indices.begin(), indices.end(), indices_.begin());
87 std::copy(priorities.begin(), priorities.end(), priorities_.begin());
88 heap_size_ = indices.size();
89 heap_positions_.resize(universe_size, kNonExistent);
90 for (HeapIndex i = 0; i < data_.size(); ++i) {
91 heap_positions_[indices_[i]] = i;
92 }
93 BuildHeap();
94 }
95
96 // Removes the top element from the heap (smallest for min-heap, largest for
97 // max-heap), and rearranges the heap.
98 // This will CHECK-fail if the heap is empty (through Top()).
99 void Pop() {
100 CHECK(!IsEmpty());
101 CHECK(RemoveAtHeapPosition(0));
102 }
103
104 // Returns the index of the top element, without modifying the heap.
105 // Note that this does not remove the element from the heap, Pop() must be
106 // called explicitly.
107 Index TopIndex() const {
108 CHECK(!IsEmpty());
109 return data_[0].second;
110 }
111
112 // Returns the index of the top element, without modifying the heap.
113 // Note that this does not remove the element from the heap, Pop() must be
114 // called explicitly.
115
116 Priority TopPriority() const {
117 CHECK(!IsEmpty());
118 return data_[0].first;
119 }
120
121 // Returns the number of elements in the heap.
122 HeapIndex heap_size() const { return heap_size_; }
123
124 // True iff the heap is empty.
125 bool IsEmpty() const { return heap_size() == 0; }
126
127 // Insert an element into the heap.
128 void Insert(Aggregate element) {
129 const Index index = element.second;
130 if (index >= heap_positions_.size()) {
131 heap_positions_.resize(index + 1, kNonExistent);
132 }
133 if (GetHeapPosition(index) == kNonExistent) {
134 heap_positions_[index] = heap_size_;
135 if (heap_size_ < data_.size()) {
136 data_[heap_size_] = element;
137 } else {
138 data_.push_back(element);
139 }
140 ++heap_size_;
141 }
142 Update(element);
143 }
144
145 // Removes the element at index. Returns false if the element does not appear
146 // in the heap.
147 bool Remove(Index index) {
148 if (IsEmpty()) return false;
149 const HeapIndex heap_position = GetHeapPosition(index);
150 return heap_position != kNonExistent ? RemoveAtHeapPosition(heap_position)
151 : false;
152 }
153
154 // Change the value of an element.
155 void Update(Aggregate element) {
156 DCHECK(!IsEmpty());
157 const HeapIndex heap_position = GetHeapPosition(element.second);
158 DCHECK_GE(heap_position, 0);
159 DCHECK_LT(heap_position, heap_positions_.size());
160 data_[heap_position] = element;
161 if (HasPriority(heap_position, Parent(heap_position))) {
162 SiftUp(heap_position);
163 } else {
164 SiftDown(heap_position);
165 }
166 }
167
168 // Checks if the element with index is in the heap.
169 bool Contains(Index index) const {
170 return GetHeapPosition(index) != kNonExistent;
171 }
172
173 // Checks that the heap is well-formed.
174 bool CheckHeapProperty() const {
175 for (HeapIndex i = heap_size() - 1; i >= Arity; --i) {
176 CHECK(HasPriority(Parent(i), i))
177 << "Parent " << Parent(i) << " with priority " << priority(Parent(i))
178 << " does not have priority over " << i << " with priority "
179 << priority(i) << " , heap_size = " << heap_size()
180 << ", priority difference = " << priority(i) - priority(Parent(i));
181 }
182 CHECK_LE(heap_size(), heap_positions_.size());
183 CHECK_LE(heap_size(), data_.size());
184 return true;
185 }
186
187 private:
188 // Gets the current position of element with index i in the heap.
189 HeapIndex GetHeapPosition(Index i) const {
190 DCHECK_GE(i, 0);
191 DCHECK_LT(i, heap_positions_.size());
192 return heap_positions_[i];
193 }
194
195 // Removes an element at a given heap position.
196 bool RemoveAtHeapPosition(HeapIndex heap_index) {
197 DCHECK(!IsEmpty());
198 DCHECK_GE(heap_index, 0);
199 if (heap_index >= heap_size()) return false;
200 PerformSwap(heap_index, heap_size() - 1);
201 --heap_size_;
202 if (HasPriority(heap_index, Parent(heap_index))) {
203 SiftUp(heap_index);
204 } else {
205 SiftDown(heap_index);
206 }
207 heap_positions_[index(heap_size_)] = kNonExistent;
208 return true;
209 }
210
211 // Maintains heap property by sifting down starting from the end,
212 void BuildHeap() {
213 for (HeapIndex i = Parent(heap_size()); i >= 0; --i) {
214 SiftDown(i);
215 }
216 DCHECK(CheckHeapProperty());
217 }
218
219 // Maintains heap property by sifting up an element.
220 void SiftUp(HeapIndex index) {
221 while (index > 0 && HasPriority(index, Parent(index))) {
222 PerformSwap(index, Parent(index));
223 index = Parent(index);
224 }
225 }
226
227 // Maintains heap property by sifting down an element.
228 void SiftDown(HeapIndex index) {
229 while (true) {
230 const HeapIndex highest_priority_child = GetHighestPriorityChild(index);
231 if (highest_priority_child == index) return;
232 PerformSwap(index, highest_priority_child);
233 index = highest_priority_child;
234 }
235 }
236
237 // Finds the child with the highest priority, i.e. the child with the
238 // smallest (resp. largest) key for a min- (resp. max-) heap.
239 // Returns index is there are no such children.
240 HeapIndex GetHighestPriorityChild(HeapIndex index) const {
241 const HeapIndex right_bound = std::min(RightChild(index) + 1, heap_size());
242 HeapIndex highest_priority_child = index;
243 for (HeapIndex i = LeftChild(index); i < right_bound; ++i) {
244 if (HasPriority(i, highest_priority_child)) {
245 highest_priority_child = i;
246 }
247 }
248 return highest_priority_child;
249 }
250
251 // Swaps two elements of data_, while also making sure heap_positions_ is
252 // properly maintained.
253 void PerformSwap(HeapIndex i, HeapIndex j) {
254 std::swap(data_[i], data_[j]);
255 std::swap(heap_positions_[index(i)], heap_positions_[index(j)]);
256 }
257
258 // Compares two elements based on whether we are dealing with a min- or a
259 // max-heap. Returns true if (data indexed by) i has more priority
260 // than j. Note that we only use operator::<.
261 bool HasPriority(HeapIndex i, HeapIndex j) const {
262 return IsMaxHeap ? data_[j] < data_[i] : data_[i] < data_[j];
263 }
264
265 // Since Arity is a (small) constant, we expect compilers to avoid
266 // multiplication instructions and use LEA instructions or a combination
267 // of shifts and arithmetic operations.
268 // Powers of 2 are guaranteed to be quick thanks to simple shifts.
269
270 // Gets the leftmost child index of a given node
271 HeapIndex LeftChild(HeapIndex index) const { return Arity * index + 1; }
272
273 // Gets the rightmost child index of a given node
274 HeapIndex RightChild(HeapIndex index) const { return Arity * (index + 1); }
275
276 // For division, the optimization is more uncertain, although a simple
277 // multiplication and a shift might be used by the compiler.
278 // Of course, powers of 2 are guaranteed to be quick thanks to simple shifts.
279
280 // Gets the parent index of a given index.
281 HeapIndex Parent(HeapIndex index) const { return (index - 1) / Arity; }
282
283 // Returns the index of the element at position i in the heap.
284 Index index(HeapIndex i) const { return data_[i].second; }
285
286 // Returns the index of the element at position i in the heap.
287 Priority priority(HeapIndex i) const { return data_[i].first; }
288
289 // The heap is stored as a vector.
290 std::vector<Aggregate> data_;
291
292 // The heap is stored as two vectors.
293 // indices_ is such that heap_positions_[indices_[i]] == i
294 // and indices_[heap_positions_[i]] == i, at all times unless
295 // indices_[i] is not in the heap, and therefore
296 // heap_positions_[indices[i]] == -1.
297 std::vector<Index> indices_;
298 std::vector<Priority> priorities_;
299
300 // Maps original index to current heap position.
301 std::vector<Index> heap_positions_;
302
303 // The number of elements currently in the heap. This may be updated
304 // either when removing an element (which is not removed from data_), or
305 // adding a new one.
306 HeapIndex heap_size_ = 0;
307
308 // The index for Aggregates not in the heap.
309 const Index kNonExistent = -1;
310};
311
312#endif // OR_TOOLS_ALGORITHMS_ADJUSTABLE_K_ARY_HEAP_H_
void Update(Aggregate element)
Change the value of an element.
AdjustableKAryHeap(const std::vector< Aggregate > &elements, HeapIndex universe_size)
Priority TopPriority() const
bool IsEmpty() const
True iff the heap is empty.
HeapIndex heap_size() const
Returns the number of elements in the heap.
void Load(const std::vector< Index > &indices, const std::vector< Priority > &priorities, HeapIndex universe_size)
bool Contains(Index index) const
Checks if the element with index is in the heap.
AdjustableKAryHeap(const std::vector< Index > &indices, const std::vector< Priority > &priorities, HeapIndex universe_size)
std::pair< Priority, Index > Aggregate
void Load(const std::vector< Aggregate > &elements, HeapIndex universe_size)
bool CheckHeapProperty() const
Checks that the heap is well-formed.
void Insert(Aggregate element)
Insert an element into the heap.
int index