From ca0e52bf670b063c7a2cfc307a86b4676862558f Mon Sep 17 00:00:00 2001
From: Martin Mares <mj@ucw.cz>
Date: Wed, 20 Mar 2019 17:56:10 +0100
Subject: [PATCH] Fibonacci heap

---
 05-fibonacci/cpp/Makefile             |  12 ++
 05-fibonacci/cpp/fibonacci.h          | 204 +++++++++++++++++++++
 05-fibonacci/cpp/fibonacci_test.cpp   | 254 ++++++++++++++++++++++++++
 05-fibonacci/cpp/test_main.cpp        |  43 +++++
 05-fibonacci/python/fibonacci.py      | 165 +++++++++++++++++
 05-fibonacci/python/fibonacci_test.py | 221 ++++++++++++++++++++++
 05-fibonacci/task.md                  |   4 +
 7 files changed, 903 insertions(+)
 create mode 100644 05-fibonacci/cpp/Makefile
 create mode 100644 05-fibonacci/cpp/fibonacci.h
 create mode 100644 05-fibonacci/cpp/fibonacci_test.cpp
 create mode 100644 05-fibonacci/cpp/test_main.cpp
 create mode 100644 05-fibonacci/python/fibonacci.py
 create mode 100644 05-fibonacci/python/fibonacci_test.py
 create mode 100644 05-fibonacci/task.md

diff --git a/05-fibonacci/cpp/Makefile b/05-fibonacci/cpp/Makefile
new file mode 100644
index 0000000..9cab32d
--- /dev/null
+++ b/05-fibonacci/cpp/Makefile
@@ -0,0 +1,12 @@
+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
diff --git a/05-fibonacci/cpp/fibonacci.h b/05-fibonacci/cpp/fibonacci.h
new file mode 100644
index 0000000..ddee892
--- /dev/null
+++ b/05-fibonacci/cpp/fibonacci.h
@@ -0,0 +1,204 @@
+#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;
+
+    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;
+};
diff --git a/05-fibonacci/cpp/fibonacci_test.cpp b/05-fibonacci/cpp/fibonacci_test.cpp
new file mode 100644
index 0000000..1a65079
--- /dev/null
+++ b/05-fibonacci/cpp/fibonacci_test.cpp
@@ -0,0 +1,254 @@
+#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.");
+            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); } },
+};
diff --git a/05-fibonacci/cpp/test_main.cpp b/05-fibonacci/cpp/test_main.cpp
new file mode 100644
index 0000000..3f4aff0
--- /dev/null
+++ b/05-fibonacci/cpp/test_main.cpp
@@ -0,0 +1,43 @@
+#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;
+}
diff --git a/05-fibonacci/python/fibonacci.py b/05-fibonacci/python/fibonacci.py
new file mode 100644
index 0000000..e05c156
--- /dev/null
+++ b/05-fibonacci/python/fibonacci.py
@@ -0,0 +1,165 @@
+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
+
+    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 wheter 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: Disconect 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
diff --git a/05-fibonacci/python/fibonacci_test.py b/05-fibonacci/python/fibonacci_test.py
new file mode 100644
index 0000000..aeaf8bb
--- /dev/null
+++ b/05-fibonacci/python/fibonacci_test.py
@@ -0,0 +1,221 @@
+#!/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."
+
+    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))
diff --git a/05-fibonacci/task.md b/05-fibonacci/task.md
new file mode 100644
index 0000000..2e04802
--- /dev/null
+++ b/05-fibonacci/task.md
@@ -0,0 +1,4 @@
+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.
-- 
GitLab