Commit d074d45a authored by Martin Mareš's avatar Martin Mareš

Fibonacci heap

parent bc177488
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."