Skip to content

Commit f8d5510

Browse files
author
Release Manager
committed
gh-39046: add a pairing heap data structure This PR adds a pairing heap data structure, that is a min-heap data structure with decrease key operation (see https://en.wikipedia.org/wiki/Pairing_heap). 3 implementations are proposed: - one with fixed capacity, items in range $0..n-1$ and values of any comparable type. - one with fixed capacity accepting any type of hashable items. Values can be of any comparable type. - one with unbounded capacity where the types of items and values must be defined at C level. It is an interface to a templated C++ implementation and is for internal use only. The 2 other implementations can be accessed from the shell. This can be useful for example for algorithms Dijkstra. ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> - [x] The title is concise and informative. - [x] The description explains in detail what this PR is about. - [ ] I have linked a relevant issue or discussion. - [x] I have created tests covering the changes. - [x] I have updated the documentation and checked the documentation preview. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on. For example, --> <!-- - #12345: short description why this is a dependency --> <!-- - #34567: ... --> URL: #39046 Reported by: David Coudert Reviewer(s): cyrilbouvier, David Coudert, gmou3, user202729
2 parents 65770e4 + e15825a commit f8d5510

File tree

6 files changed

+1915
-0
lines changed

6 files changed

+1915
-0
lines changed

src/doc/en/reference/data_structures/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ Data Structures
99
sage/data_structures/bounded_integer_sequences
1010
sage/data_structures/stream
1111
sage/data_structures/mutable_poset
12+
sage/data_structures/pairing_heap
1213

1314
.. include:: ../footer.txt

src/doc/en/reference/references/index.rst

+5
Original file line numberDiff line numberDiff line change
@@ -2773,6 +2773,11 @@ REFERENCES:
27732773
Cambridge University Press, Cambridge, 2009.
27742774
See also the `Errata list <http://ac.cs.princeton.edu/errata/>`_.
27752775
2776+
.. [FSST1986] Michael L. Fredman, Robert Sedgewick, Daniel D. Sleator,
2777+
and Robert E. Tarjan. *The pairing heap: A new form of
2778+
self-adjusting heap*, Algorithmica, 1:111-129, 1986.
2779+
:doi:`10.1007/BF01840439`
2780+
27762781
.. [FST2012] \A. Felikson, \M. Shapiro, and \P. Tumarkin, *Cluster Algebras of
27772782
Finite Mutation Type Via Unfoldings*, Int Math Res Notices (2012)
27782783
2012 (8): 1768-1804.

src/sage/data_structures/meson.build

+17
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ py.install_sources(
99
'bounded_integer_sequences.pxd',
1010
'list_of_pairs.pxd',
1111
'mutable_poset.py',
12+
'pairing_heap.h',
13+
'pairing_heap.pxd',
1214
'sparse_bitset.pxd',
1315
'stream.py',
1416
subdir: 'sage/data_structures',
@@ -39,3 +41,18 @@ foreach name, pyx : extension_data
3941
)
4042
endforeach
4143

44+
extension_data_cpp = {
45+
'pairing_heap' : files('pairing_heap.pyx'),
46+
}
47+
48+
foreach name, pyx : extension_data_cpp
49+
py.extension_module(
50+
name,
51+
sources: pyx,
52+
subdir: 'sage/data_structures/',
53+
install: true,
54+
override_options: ['cython_language=cpp'],
55+
include_directories: [inc_cpython, inc_data_structures],
56+
dependencies: [py_dep, cysignals, gmp],
57+
)
58+
endforeach
+344
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
/*
2+
* Pairing heap
3+
*
4+
* Implements a pairing heap data structure as described in [1]. See also [2]
5+
* for more details.
6+
*
7+
* This implementation is templated by the type TI of items and the type TV of
8+
* the value associated with an item. The type TI must be either a standard type
9+
* (int, size_t, etc.) or a type equipped with a has function as supported by
10+
* std::unordered_map. The top of the heap is the item with smallest value,
11+
* i.e., this is a min heap data structure. The number of items in the heap is
12+
* not fixed. It supports the following operations:
13+
*
14+
* - empty(): return true if the heap is empty, and false otherwise.
15+
*
16+
* - push(item, value): push an item to the heap with specified value.
17+
*
18+
* - top(): access the pair (item, value) at the top of the heap, i.e., with
19+
* smallest value in time O(1).
20+
* This operation assumes that the heap is not empty.
21+
*
22+
* - top_item(): access the item at the top of the heap in time O(1).
23+
* This operation assumes that the heap is not empty.
24+
*
25+
* - top_value(): access the value of the item at the top of the heap in O(1).
26+
* This operation assumes that the heap is not empty.
27+
*
28+
* - pop(): remove top item from the heap in amortize time O(log(n)).
29+
*
30+
* - decrease(item, new_value): change the value associated with the item to the
31+
* specified value ``new_value`` in time o(log(n)). The new value must be
32+
* smaller than the previous one. Otherwise the structure of the heap is no
33+
* longer guaranteed.
34+
* If the item is not already in the heap, this method calls method ``push``.
35+
*
36+
* - contains(item): check whether specified item is in the heap in time O(1).
37+
*
38+
* - value(item): return the value associated with the item in the heap.
39+
* This operation assumes that the item is already in the heap.
40+
*
41+
* References:
42+
*
43+
* [1] M. L. Fredman, R. Sedgewick, D. D. Sleator, and R. E. Tarjan.
44+
* "The pairing heap: a new form of self-adjusting heap".
45+
* Algorithmica. 1 (1): 111-129, 1986. doi:10.1007/BF01840439.
46+
*
47+
* [2] https://en.wikipedia.org/wiki/Pairing_heap
48+
*
49+
* Author:
50+
* - David Coudert <[email protected]>
51+
*
52+
*/
53+
54+
#ifndef PAIRING_HEAP_H
55+
#define PAIRING_HEAP_H
56+
57+
#include <stdexcept>
58+
#include <unordered_map>
59+
60+
namespace pairing_heap {
61+
62+
template<
63+
typename TV, // type of values
64+
typename T // type of the child class
65+
>
66+
struct PairingHeapNodeBase {
67+
public:
68+
69+
bool operator<=(PairingHeapNodeBase const& other) const {
70+
return static_cast<T const*>(this)->le_implem(static_cast<T const&>(other));
71+
}
72+
73+
// Pair list of heaps and return pointer to the top of resulting heap
74+
static T *_pair(T *p) {
75+
if (p == nullptr) {
76+
return nullptr;
77+
}
78+
79+
/*
80+
* Move toward the end of the list, counting elements along the way.
81+
* This is done in order to:
82+
* - know whether the list has odd or even number of nodes
83+
* - speed up going-back through the list
84+
*/
85+
size_t children = 1;
86+
T *it = p;
87+
while (it->next != nullptr) {
88+
it = it->next;
89+
children++;
90+
}
91+
92+
T *result;
93+
94+
if (children % 2 == 1) {
95+
T *a = it;
96+
it = it->prev;
97+
a->prev = a->next = nullptr;
98+
result = a;
99+
} else {
100+
T *a = it;
101+
T *b = it->prev;
102+
it = it->prev->prev;
103+
a->prev = a->next = b->prev = b->next = nullptr;
104+
result = _merge(a, b);
105+
}
106+
107+
for (size_t i = 0; i < (children - 1) / 2; i++) {
108+
T *a = it;
109+
T *b = it->prev;
110+
it = it->prev->prev;
111+
a->prev = a->next = b->prev = b->next = nullptr;
112+
result = _merge(_merge(a, b), result);
113+
}
114+
115+
return result;
116+
} // end _pair
117+
118+
119+
// Merge 2 heaps and return pointer to the top of resulting heap
120+
static T *_merge(T *a, T *b) {
121+
if (*a <= *b) { // Use comparison method of PairingHeapNodeBase
122+
_link(a, b);
123+
return a;
124+
} else {
125+
_link(b, a);
126+
return b;
127+
}
128+
} // end _merge
129+
130+
131+
// Make b a child of a
132+
static void _link(T *a, T *b) {
133+
if (a->child != nullptr) {
134+
b->next = a->child;
135+
a->child->prev = b;
136+
}
137+
b->prev = a;
138+
a->child = b;
139+
} // end _link
140+
141+
142+
// Remove p from its parent children list
143+
static void _unlink(T *p) {
144+
if (p->prev->child == p) {
145+
p->prev->child = p->next;
146+
} else {
147+
p->prev->next = p->next;
148+
}
149+
if (p->next != nullptr) {
150+
p->next->prev = p->prev;
151+
}
152+
p->prev = nullptr;
153+
p->next = nullptr;
154+
} // end _unlink
155+
156+
157+
TV value; // value associated to the node
158+
T * prev; // Previous sibling of the node or parent
159+
T * next; // Next sibling of the node
160+
T * child; // First child of the node
161+
162+
protected:
163+
// Only derived class can build a PairingHeapNodeBase
164+
explicit PairingHeapNodeBase(const TV &some_value)
165+
: value{some_value}, prev{nullptr}, next{nullptr}, child{nullptr} {
166+
}
167+
}; // end struct PairingHeapNodeBase
168+
169+
170+
template<
171+
typename TI, // type of items stored in the node
172+
typename TV // type of values associated with the stored item
173+
// Assumes TV is a comparable type
174+
>
175+
class PairingHeapNode
176+
: public PairingHeapNodeBase<TV, PairingHeapNode<TI, TV>> {
177+
178+
public:
179+
PairingHeapNode(TI const& some_item, TV const& some_value)
180+
: Base_(some_value), item(some_item) {
181+
}
182+
183+
bool le_implem(PairingHeapNode const& other) const {
184+
return this->value <= other.value;
185+
}
186+
187+
TI item; // item contained in the node
188+
189+
private:
190+
using Base_ = PairingHeapNodeBase<TV, PairingHeapNode<TI, TV>>;
191+
};
192+
193+
194+
class PairingHeapNodePy
195+
: public PairingHeapNodeBase<PyObject *, PairingHeapNodePy> {
196+
public:
197+
PairingHeapNodePy(PyObject *some_value)
198+
: Base_(some_value) {
199+
}
200+
201+
bool le_implem(PairingHeapNodePy const& other) const {
202+
return PyObject_RichCompareBool(this->value, other.value, Py_LE);
203+
}
204+
205+
private:
206+
using Base_ = PairingHeapNodeBase<PyObject *, PairingHeapNodePy>;
207+
};
208+
209+
210+
211+
template<
212+
typename TI, // type of items stored in the node
213+
typename TV // type of values associated with the stored item
214+
// Assume TV is a comparable type
215+
>
216+
class PairingHeap
217+
{
218+
public:
219+
using HeapNodeType = PairingHeapNode<TI, TV>;
220+
221+
// Constructor
222+
explicit PairingHeap()
223+
: root(nullptr) {
224+
}
225+
226+
// Copy constructor
227+
PairingHeap(PairingHeap<TI, TV> const *other)
228+
: root(nullptr) {
229+
for (auto const& it: other->nodes) {
230+
push(it.first, it.second->value);
231+
}
232+
}
233+
234+
// Destructor
235+
virtual ~PairingHeap() {
236+
for (auto const& it: nodes) {
237+
delete it.second;
238+
}
239+
}
240+
241+
// Return true if the heap is empty, else false
242+
bool empty() const {
243+
return root == nullptr;
244+
}
245+
246+
// Return true if the heap is not empty, else false
247+
explicit operator bool() const {
248+
return root != nullptr;
249+
}
250+
251+
// Insert an item into the heap with specified value (priority)
252+
void push(const TI &some_item, const TV &some_value) {
253+
if (contains(some_item)) {
254+
throw std::invalid_argument("item already in the heap");
255+
}
256+
PairingHeapNode<TI, TV> *p = new PairingHeapNode<TI, TV>(some_item, some_value);
257+
nodes[some_item] = p;
258+
root = empty() ? p : HeapNodeType::_merge(root, p);
259+
}
260+
261+
// Return the top pair (item, value) of the heap
262+
std::pair<TI, TV> top() const {
263+
if (empty()) {
264+
throw std::domain_error("trying to access the top of an empty heap");
265+
}
266+
return std::make_pair(root->item, root->value);
267+
}
268+
269+
// Return the top item of the heap
270+
TI top_item() const {
271+
if (empty()) {
272+
throw std::domain_error("trying to access the top of an empty heap");
273+
}
274+
return root->item;
275+
}
276+
277+
// Return the top value of the heap
278+
TV top_value() const {
279+
if (empty()) {
280+
throw std::domain_error("trying to access the top of an empty heap");
281+
}
282+
return root->value;
283+
}
284+
285+
// Remove the top element from the heap. Do nothing if empty
286+
void pop() {
287+
if (not empty()) {
288+
PairingHeapNode<TI, TV> *p = root->child;
289+
nodes.erase(root->item);
290+
delete root;
291+
root = HeapNodeType::_pair(p);
292+
}
293+
}
294+
295+
// Decrease the value of specified item
296+
// If the item is not in the heap, push it
297+
void decrease(const TI &some_item, const TV &new_value) {
298+
if (contains(some_item)) {
299+
PairingHeapNode<TI, TV> *p = nodes[some_item];
300+
if (p->value <= new_value) {
301+
throw std::invalid_argument("the new value must be less than the current value");
302+
}
303+
p->value = new_value;
304+
if (p->prev != nullptr) {
305+
HeapNodeType::_unlink(p);
306+
root = HeapNodeType::_merge(root, p);
307+
}
308+
} else {
309+
push(some_item, new_value);
310+
}
311+
}
312+
313+
// Check if specified item is in the heap
314+
bool contains(TI const& some_item) const {
315+
return nodes.find(some_item) != nodes.end();
316+
}
317+
318+
// Return the value associated with the item
319+
TV value(const TI &some_item) const {
320+
auto it = nodes.find(some_item);
321+
if (it == nodes.end()) {
322+
throw std::invalid_argument("the specified item is not in the heap");
323+
}
324+
return it->second->value;
325+
}
326+
327+
// Return the number of items in the heap
328+
size_t size() const {
329+
return nodes.size();
330+
}
331+
332+
private:
333+
334+
// Pointer to the top of the heap
335+
PairingHeapNode<TI, TV> *root;
336+
337+
// Map used to access stored items
338+
std::unordered_map<TI, PairingHeapNode<TI, TV> *> nodes;
339+
340+
}; // end class PairingHeap
341+
342+
} // end namespace pairing_heap
343+
344+
#endif

0 commit comments

Comments
 (0)