Skip to content
Snippets Groups Projects
Commit d074d45a authored by Martin Mareš's avatar Martin Mareš
Browse files

Fibonacci heap

parent bc177488
No related branches found
No related tags found
No related merge requests found
test: fibonacci_test
./$<
CXXFLAGS=-std=c++11 -O2 -Wall -Wextra -g -Wno-sign-compare
fibonacci_test: fibonacci.h fibonacci_test.cpp test_main.cpp
$(CXX) $(CXXFLAGS) fibonacci_test.cpp test_main.cpp -o $@
clean::
rm -f fibonacci_test
.PHONY: clean test
#include <vector>
// If the condition is not true, report an error and halt.
#define EXPECT(condition, message) do { if (!(condition)) expect_failed(message); } while (0)
void expect_failed(const std::string& message);
/*
* payload_t: Type used to allow storing user data in each heap node.
*
* priority_t: Type used to represent priority of the node. It must
* support comparison.
*
* In proper C++, the heap should be a template parametrized by these
* types, but we decided to keep the code as simple as possible.
*/
typedef int payload_t;
typedef int priority_t;
struct FibonacciHeap;
// Node of FibonacciHeap
struct Node {
payload_t payload;
priority_t priority() { return prio; }
private:
priority_t prio;
Node *parent = nullptr, *first_child = nullptr;
// Pointers in a cyclic list of children of the parent.
Node *prev_sibling, *next_sibling;
int rank = 0;
bool marked = false;
Node(priority_t p, payload_t payload) : payload(payload), prio(p) {
next_sibling = prev_sibling = this;
}
~Node() {}
friend class FibonacciHeap;
friend class HeapTests;
};
/* FibonacciHeap
*
* Implemented using a meta root to avoid special handling of the
* linked list of heap trees.
*/
struct FibonacciHeap {
// Check whether the heap is empty
bool is_empty() { return size == 0; }
/* Insert a new node with given priority and payload and return it.
*
* `payload` can be used to store any data in the node.
*/
Node *insert(priority_t prio, payload_t payload = payload_t()) {
Node *n = new Node(prio, payload);
add_child(&meta_root, n);
size++;
return n;
}
/* Extract node with minimum priority.
*
* Must not be called when the heap is empty.
* Returns pair (payload, priority) of the removed node.
*/
std::pair<payload_t, priority_t> extract_min() {
EXPECT(meta_root.rank > 0, "Cannot extract minimum of an empty heap.");
// Find the tree whose root is minimal.
Node *min = meta_root.first_child;
Node *node = min->next_sibling;
while (node != meta_root.first_child) {
if (node->prio < min->prio) min = node;
node = node->next_sibling;
}
// Add all its subtrees to the heap.
while (min->rank > 0)
add_child(&meta_root, remove(min->first_child));
// Remove the root.
std::pair<payload_t, priority_t> ret = { min->payload, min->prio };
delete remove(min);
size--;
// Finally, consolidate the heap.
consolidate();
return ret;
}
/* Decrease priority of node to new_prio.
*
* new_prio must not be higher than node.prio.
*/
void decrease(Node *node, priority_t new_prio) {
EXPECT(node, "Cannot decrase null pointer.");
EXPECT(node->prio >= new_prio, "Decrase: new priority larget than old one.");
// TODO: Implement
}
FibonacciHeap() : size(0), meta_root(priority_t(), payload_t()) {}
~FibonacciHeap() {
if (size == 0) return;
Node *next;
Node *n = meta_root.first_child;
do {
while (n->first_child) n = n->first_child;
next = (n->next_sibling != n) ? n->next_sibling : n->parent;
delete remove(n);
} while ((n = next) != &meta_root);
}
protected:
// Consolidate heap during extract_min.
void consolidate() {
std::vector<Node*> buckets(max_rank(), nullptr);
while (meta_root.first_child) {
Node *node = remove(meta_root.first_child);
while (Node *&b = buckets.at(node->rank)) {
node = pair_trees(b, node);
b = nullptr;
}
buckets.at(node->rank) = node;
}
for (Node *node : buckets)
if (node) add_child(&meta_root, node);
}
// Join two trees of the same rank.
Node *pair_trees(Node *a, Node *b) {
if (a->prio > b->prio) std::swap(a, b);
add_child(a, b);
return a;
}
// Return maximum possible rank of a tree in a heap of the current size.
virtual size_t max_rank() {
size_t ret = 1;
while ((1 << ret) <= size) ret++;
return ret;
}
// Check whether a node is the root.
bool is_root(Node *n) {
return n->parent == &meta_root;
}
// Link together two elements of linked list -- a and b.
void join(Node *a, Node *b) {
a->next_sibling = b;
b->prev_sibling = a;
}
// Add node as a child of a given parent.
virtual void add_child(Node* parent, Node *node) {
EXPECT(parent, "add_child: Parent cannot be nullptr.");
EXPECT(node, "add_child: Node cannot be nullptr.");
EXPECT(!node->parent, "add_child: Node already has a parent.");
EXPECT(parent == &meta_root || node->rank == parent->rank,
"add_child: Ranks of node and parent are different.");
EXPECT(node->prev_sibling == node && node->next_sibling == node,
"add_child: Node has a sibling.");
EXPECT(parent == &meta_root || parent->prio <= node->prio,
"add_child: Parent has bigger priority than node.");
if (parent->rank == 0) parent->first_child = node;
else {
join(parent->first_child->prev_sibling, node);
join(node, parent->first_child);
}
node->parent = parent;
parent->rank++;
}
// Disconnect a node from its parent.
virtual Node *remove(Node *n) {
EXPECT(n->parent, "remove: Cannot disconnect node without parent.");
EXPECT(n != &meta_root, "remove: Cannot remove meta root.");
n->parent->rank--;
if (n->parent->first_child == n) n->parent->first_child = n->next_sibling;
if (n->parent->rank == 0) n->parent->first_child = nullptr;
n->parent = nullptr;
join(n->prev_sibling, n->next_sibling);
join(n, n);
return n;
}
friend class HeapTests;
size_t size;
Node meta_root;
};
#include <stdio.h>
#include <utility>
#include <functional>
#include <string>
#include <limits>
#include <algorithm>
#include "fibonacci.h"
struct HeapTests {
using Heap = FibonacciHeap;
// Good enough infinity for our tests.
static const int infty = 1000*1000*1000;
/* Iterator over all children of given node.
* Also implements begin() and end() so it can
* be plugged into for-each cycle.
*/
class NodeIterator {
Node *root;
Node *node;
bool recursive;
public:
NodeIterator begin() { return *this; }
NodeIterator end() { return NodeIterator(); }
bool operator !=(const NodeIterator& b) { return node != b.node; }
Node *operator*() { return node; }
NodeIterator &operator++() {
if (recursive && node->rank > 0) {
node = node->first_child;
} else {
while (node->next_sibling == node->parent->first_child) {
node = node->parent;
if (node == root) {
node = nullptr;
return *this;
}
}
node = node->next_sibling;
}
return *this;
}
NodeIterator(Node *root = nullptr, bool recursive = true) : root(root),
node(root ? root->first_child : nullptr), recursive(recursive) {}
};
// Helper of show_heap.
static void show_node(Node *node, int indent) {
for (int i = 0; i < indent; i++) printf(" ");
printf("- %i (payload: %i)\n", node->priority(), node->payload);
for (Node* child : NodeIterator(node, false))
show_node(child, indent + 1);
}
// Show heap in human-readable form.
static void show_heap(Heap* H) {
printf("Heap of size %i\n", (int)H->size);
for (Node* root : NodeIterator(&H->meta_root, false))
show_node(root, 0);
printf("\n");
}
// Check heap invariants.
static void audit_heap(Heap* H) {
int size = 0;
for (Node* node : NodeIterator(&H->meta_root)) {
size++;
EXPECT(H->is_root(node) || node->parent->priority() <= node->priority(),
"Parent has bigger priority than its child.");
EXPECT(!H->is_root(node) || !node->marked, "A root is marked!");
std::vector<int> children;
for (Node *c : NodeIterator(node, false)) {
children.push_back(c->rank);
EXPECT(c->parent == node, "Inconsistent parent pointer.");
EXPECT(c->next_sibling->prev_sibling == c, "Inconsistent sibling pointers.");
EXPECT(c->prev_sibling->next_sibling == c, "Inconsistent sibling pointers.");
}
std::sort(children.begin(), children.end());
EXPECT(children.size() == node->rank,
"Rank of node does not match its real number of children.");
for (int i = 0; i < children.size(); i++)
EXPECT(children[i] >= i - 1, "Child of the node has too small rank.");
}
EXPECT(size == H->size, "Size of the heap does not match real number of its nodes.");
}
// Remove given node from heap.
static void remove(Heap* H, Node *n) {
H->decrease(n, -infty);
H->extract_min();
}
// Force consolidation of the heap.
static void consolidate(Heap* H) {
remove(H, H->insert(0));
}
/* Remove the subtree rooted in node from the heap.
*
* Note that this method slightly cheats because it
* learns what nodes are in the subtree by walking over it.
*/
static void remove_subtree(Heap* H, Node *node) {
std::vector<Node*> to_remove;
for (Node* n : NodeIterator(node)) to_remove.push_back(n);
to_remove.push_back(node);
for (Node* n : to_remove) remove(H, n);
}
/* Build a tree of a given rank with as few nodes as possible.
*
* Return pair (root of the tree, its child with the largest rank).
*/
static std::pair<Node*,Node*> build_sparse_tree(Heap *H, int rank, int prio) {
if (rank == 0) return { H->insert(prio), nullptr };
auto A = build_sparse_tree(H, rank - 1, prio);
auto B = build_sparse_tree(H, rank - 1, prio + 1);
consolidate(H);
if (B.second) remove_subtree(H, B.second);
return { A.first, B.first };
}
// Check whether H contains exacly one path and nothing else.
static void check_path(Heap* H) {
Node *node = &H->meta_root;
while (node->rank == 1) node = node->first_child;
EXPECT(node->rank == 0, "Expected path but found node of rank larger than 1.");
}
/* Build a path of a given length.
*
* Return lowest node on this path. All nodes on path are marked.
*/
static Node* build_path(Heap *H, int length) {
Node *a, *b;
std::vector<Node*> to_remove;
H->insert(length + 1);
Node *end = H->insert(length + 2);
consolidate(H);
to_remove.push_back(H->insert(length + 2));
a = H->insert(length + 3);
consolidate(H);
remove(H, a);
for (int i = length; i > 0; i--) {
H->insert(i);
to_remove.push_back(H->insert(i + 1));
consolidate(H);
a = H->insert(i + 1);
b = H->insert(i + 2);
consolidate(H);
remove(H, b);
remove(H, a);
}
for (Node *n : to_remove) remove(H, n);
check_path(H);
return end;
}
/* A simple random test.
*
* It does in order:
* - randomly insert elements,
* - do random decreases,
* - extract_min until the heap is empty.
*/
static void test_random(int size, bool print) {
Heap H;
std::vector<std::pair<Node*, int>> node_map;
for (int i = 0; i < size; i++) {
int prio = (1009 * i) % 2099;
node_map.push_back({ H.insert(prio, i), prio });
}
consolidate(&H);
if (print) show_heap(&H);
for (int i = 0; i < size; i++) {
if (i % (size / 10) == 0) audit_heap(&H);
int idx = (i * 839) % node_map.size();
auto& X = node_map[idx];
EXPECT(X.first->priority() == X.second,
"Priority of node changed but its was not decreased.");
int new_prio = X.second - ((i * 131 + 15) % 239);
H.decrease(X.first, new_prio);
EXPECT(X.first->priority() == new_prio,
"Decrease failed to change priority of the node.");
if (print) {
printf("Decrease %i -> %i (payload %i)\n", X.second, new_prio, X.first->payload);
show_heap(&H);
}
X.second = new_prio;
}
int last = std::numeric_limits<int>::min();
audit_heap(&H);
while (!H.is_empty()) {
auto x = H.extract_min();
payload_t payload = x.first;
priority_t prio = x.second;
EXPECT(last <= prio, "extract_min is not monotone.");
EXPECT(node_map[payload].first,
"Extracted node was already deleted or something changed its payload.");
EXPECT(prio == node_map[payload].second,
"Priority of extracted node is wrong.");
last = prio;
node_map[payload].first = nullptr;
if (print) {
printf("Extract min %i (payload %i)\n", prio, payload);
show_heap(&H);
}
}
for (auto& X : node_map) EXPECT(X.first == nullptr, "Not all nodes were deleted.");
}
// Build a sparse tree of given rank and then empty the heap.
static void test_sparse_tree(int rank) {
Heap H;
build_sparse_tree(&H, rank, 1);
audit_heap(&H);
while (!H.is_empty()) H.extract_min();
}
// Build a path, decrease it lowest node, and empty the heap.
static void test_path(int length) {
Heap H;
H.decrease(build_path(&H, length), -infty);
for (Node *n : NodeIterator(&H.meta_root))
EXPECT(n->rank == 0, "Decrease of lowest node should have broken "
"the path into forest of isolated vertices but it has not.");
audit_heap(&H);
while (!H.is_empty()) H.extract_min();
}
};
std::vector<std::pair<std::string, std::function<void()>>> tests = {
{ "small", [] { HeapTests::test_random(11, true); } },
{ "random", [] { HeapTests::test_random(10000, false); } },
{ "sparse", [] { HeapTests::test_sparse_tree(16); } },
{ "path", [] { HeapTests::test_path(5000); } },
};
#include <cstdlib>
#include <functional>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
using namespace std;
extern vector<pair<string, function<void()>>> tests;
void expect_failed(const string& message) {
cerr << "Test error: " << message << endl;
exit(1);
}
int main(int argc, char* argv[]) {
vector<string> required_tests;
if (argc > 1) {
required_tests.assign(argv + 1, argv + argc);
} else {
for (const auto& test : tests)
required_tests.push_back(test.first);
}
for (const auto& required_test : required_tests) {
bool found = false;
for (const auto& test : tests)
if (required_test == test.first) {
cerr << "Running test " << required_test << endl;
test.second();
found = true;
break;
}
if (!found) {
cerr << "Unknown test " << required_test << endl;
return 1;
}
}
return 0;
}
class Node:
"""Node of `FibonacciHeap`.
Property `payload` can be used to store any information
the user needs.
"""
def __init__(self, prio, payload = None):
self.payload = payload
self._prio = prio
self._parent = None
self._rank = 0
self._first_child = None
# Pointers in a cyclic list of children of the parent.
self._prev_sibling = self
self._next_sibling = self
self._marked = False
def priority(self):
"""Return priority of the node."""
return self._prio
class FibonacciHeap:
"""Fibonacci heap.
Implemented using a meta root to avoid special handling of the
linked list of heap trees.
"""
def __init__(self):
self._size = 0
self._meta_root = Node(None)
def is_empty(self):
"""Check whether the heap is empty."""
return self._size == 0
def insert(self, prio, payload = None):
"""Insert a new node with given priority and payload and return it.
`payload` can be used to store any data in the node.
"""
n = Node(prio, payload)
self._add_child(self._meta_root, n)
self._size += 1
return n
def extract_min(self):
"""Extract node with minimum priority.
Must not be called when the heap is empty.
Returns tuple (payload, priority) of the removed node.
"""
assert self._size > 0, "Cannot extract minimum of an empty heap."
# Find the tree whose root is minimal.
minimum = self._meta_root._first_child
node = self._meta_root._first_child._next_sibling
while node is not self._meta_root._first_child:
if node._prio < minimum._prio:
minimum = node
node = node._next_sibling
self._remove(minimum)
self._size -= 1
# Add all its subtrees to the heap.
while minimum._rank > 0:
self._add_child(self._meta_root, self._remove(minimum._first_child))
# Finally, consolidate the heap.
self._consolidate()
return (minimum.payload, minimum._prio)
def _consolidate(self):
"""INTERNAL: Consolidate heap during extract_min.
"""
buckets = [ None ] * self._max_rank()
while self._meta_root._rank > 0:
node = self._remove(self._meta_root._first_child)
while buckets[node._rank] is not None:
b = node._rank
node = self._pair_trees(node, buckets[b])
buckets[b] = None
buckets[node._rank] = node
for node in buckets:
if node is not None:
self._add_child(self._meta_root, node)
def _pair_trees(self, a, b):
"""INTERNAL: Join two trees of the same rank.
"""
if a._prio > b._prio: a, b = b, a
self._add_child(a, b)
return a
def _max_rank(self):
"""INTERNAL: Return maximum possible rank of a tree in a heap of the current size.
"""
ret = 1
while (1 << ret) <= self._size: ret += 1
return ret
def decrease(self, node, new_prio):
"""Decrease priority of node to new_prio.
new_prio must not be higher than node._prio.
"""
assert node is not None, "Cannot decrease None."
assert node is not self._meta_root, "Cannot decrease meta root."
assert new_prio <= node._prio, "Decrease: new priority is bigger than old one."
# TODO: Implement
raise NotImplementedError
def _is_root(self, node):
"""Check whether node is root."""
return node._parent is self._meta_root
def _add_child(self, parent, node):
"""INTERNAL: Add node as child of parent.
"""
assert parent is not None, "_add_child: Parent cannot be None."
assert node is not None, "_add_child: Cannot add None as a child."
assert node._parent is None, "_add_child: node already has a parent."
assert parent is self._meta_root or node._rank == parent._rank, \
"_add_child: ranks of node and parent are different"
assert node._prev_sibling is node and node._next_sibling is node, \
"_add_child: node has a sibling"
assert parent is self._meta_root or parent._prio <= node._prio, \
"_add_child: parent has bigger priority than node"
if parent._rank == 0:
parent._first_child = node
else:
self._join(parent._first_child._prev_sibling, node)
self._join(node, parent._first_child)
node._parent = parent
parent._rank += 1
def _remove(self, node):
"""INTERNAL: Disconnect node from parent.
"""
assert node._parent is not None, "_remove: Cannot disconnect node without parent."
assert node is not self._meta_root, "_remove: Cannot remove meta root."
node._parent._rank -= 1
if node._parent._rank == 0:
node._parent._first_child = None
elif node._parent._first_child is node:
node._parent._first_child = node._next_sibling
node._parent = None
self._join(node._prev_sibling, node._next_sibling)
self._join(node, node)
return node
def _join(self, a, b):
"""INTERNAL: Link together two elements of linked list -- a and b.
"""
a._next_sibling = b
b._prev_sibling = a
#!/usr/bin/env python3
from fibonacci import FibonacciHeap as Heap
from math import log
def children(root, recursive = True):
"""Iterate over all children of `root`.
"""
if root._first_child is None: return
node = root._first_child
while True:
yield node
if recursive and node._rank > 0:
node = node._first_child
else:
while node._next_sibling is node._parent._first_child:
node = node._parent
if node is root: return
node = node._next_sibling
def show_node(node, indent):
"""Helper of show_heap.
"""
print("%s- %s (payload: %s)" % (" " * indent, node.priority(), node.payload))
for child in children(node, False):
show_node(child, indent + 1)
def show_heap(heap):
"""Show heap in human-readable form.
"""
print("Heap of size %i" % heap._size)
for root in children(heap._meta_root, False):
show_node(root, 0)
print("")
def audit_heap(heap):
"""Check various heap invariants.
"""
assert heap._meta_root is not None, "Meta root is None"
assert heap._size == sum( 1 for _ in children(heap._meta_root) ), \
"Size of the heap does not match real number of its nodes."
assert all( root._marked == False for root in children(heap._meta_root, \
recursive=False) ), "A root is marked."
for root in children(heap._meta_root):
assert heap._is_root(root) or root._parent.priority() <= root.priority(), \
"Parent has bigger priority than its child."
child_sizes = []
for c in children(root, False):
child_sizes.append(c._rank)
assert c._parent is root, "Inconsistent parent pointer."
assert c._next_sibling._prev_sibling is c, "Inconsistent sibling pointers."
assert c._prev_sibling._next_sibling is c, "Inconsistent sibling pointers."
assert len(child_sizes) == root._rank, \
"Rank of node does not match its real number of children."
for i, s in enumerate(sorted(child_sizes)):
assert s >= i - 1, "Child of the node has too small rank."
def remove(H, node):
"""Remove node from heap H.
"""
H.decrease(node, -10**9)
H.extract_min()
def consolidate(H):
"""Force consolidation of heap H.
"""
remove(H, H.insert(0))
def remove_subtree(H, node):
"""Remove subtree rooted in node from the heap.
Note that this method slightly cheats because it
learns what nodes are in the subtree by walking over it.
"""
to_remove = list(children(node))
to_remove.append(node)
for node in to_remove: remove(H, node)
def build_sparse_tree(H, rank, prio):
"""Build a tree of a given rank with as few nodes as possible.
Return pair (root of the tree, its child with largest rank).
"""
if rank == 0:
return (H.insert(prio), None)
a, _ = build_sparse_tree(H, rank - 1, prio)
b, to_remove = build_sparse_tree(H, rank - 1, prio + 1)
consolidate(H)
if to_remove is not None:
remove_subtree(H, to_remove)
return (a, b)
def check_path(H):
"""Check whether H contains exactly one path and nothing else.
"""
node = H._meta_root
while node._rank == 1: node = node._first_child
assert node._rank == 0, "Expected path but found node of rank larger than 1."
def build_path(H, length):
"""Build a path of given length.
Return lowest node on this path. All nodes on path are marked.
"""
to_remove = []
H.insert(length + 1)
end = H.insert(length + 2)
consolidate(H)
to_remove.append(H.insert(length + 2))
a = H.insert(length + 3)
consolidate(H)
remove(H, a)
for i in range(length, 0, -1):
H.insert(i)
to_remove.append(H.insert(i + 1))
consolidate(H)
a = H.insert(i + 1)
b = H.insert(i + 2)
consolidate(H)
remove(H, b)
remove(H, a)
for n in to_remove:
remove(H, n)
check_path(H)
return end
def test_random(size, do_print):
"""A simple random test.
It does in order:
- randomly insert elements,
- do random decreases,
- extract_min until the heap is empty.
"""
H = Heap()
node_map = []
for i in range(size):
prio = (1009 * i) % 2099
node_map.append([H.insert(prio, payload=i), prio])
consolidate(H)
if do_print: show_heap(H)
for i in range(size):
if i % (size // 10) == 0:
audit_heap(H)
idx = (i * 839) % len(node_map)
n, prio = node_map[idx]
assert n.priority() == prio, "Priority of node changed but its was not decreased."
new_prio = prio - ((i * 131 + 15) % 239)
H.decrease(n, new_prio)
assert n.priority() == new_prio, "Decrease failed to change priority of the node."
node_map[idx][1] = new_prio
if do_print:
print("Decrease %s -> %s (payload %s)" % (prio, new_prio, n.payload))
show_heap(H)
last = None
audit_heap(H)
while not H.is_empty():
payload, prio = H.extract_min()
assert last is None or prio >= last, "extract_min is not monotone."
assert node_map[payload][0], \
"Extracted node was already deleted or something changed its payload."
assert prio == node_map[payload][1], "Priority of extracted node is wrong."
last = prio
node_map[payload] = None
if do_print:
print("Extract min %s (payload %s)" % (prio, payload))
show_heap(H)
assert all( x is None for x in node_map ), "Not all nodes were deleted."
def test_sparse_tree(rank):
"""Build a sparse tree of given rank and then empty the heap."""
H = Heap()
build_sparse_tree(H, rank, 1)
audit_heap(H)
while not H.is_empty():
H.extract_min()
def test_path(length):
"""Build a path, decrease it lowest node, and empty the heap."""
H = Heap()
H.decrease(build_path(H, length), -1000)
assert all( n._rank == 0 for n in children(H._meta_root, False) ), \
"Decrease of lowest node should have broken the path into forest " + \
"of isolated vertices but it did not."
audit_heap(H)
while not H.is_empty():
H.extract_min()
tests = [
("small", lambda: test_random(11, True)),
("random", lambda: test_random(10000, False)),
("sparse", lambda: test_sparse_tree(16)),
("path", lambda: test_path(5000)),
]
if __name__ == "__main__":
import sys
for required_test in sys.argv[1:] or [name for name, _ in tests]:
for name, test in tests:
if name == required_test:
print("Running test {}".format(name), file=sys.stderr)
test()
break
else:
raise ValueError("Unknown test {}".format(name))
You are given an implementation of a lazy binomial heap, which supports
Insert and ExtractMin operations.
Turn it into a Fibonacci heap and implement Decrease.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment