Fibonacci Heap

Prerequisite: Binomial Heap

Binomial tree of degree k k k

Definition: two k − 1 k-1 k1 binomial tree merge into a k k k binomial tree
Property:

  • the root has k k k children
  • the tree has 2 k 2^k 2k elements
  • the tree has height k k k
  • the children of binomial tree are binomial trees of degree k − 1 , k − 2 , ⋯   , 1 k-1, k-2, \cdots, 1 k1,k2,,1

Binomial Heap

consists of a collection of binomial trees, at most one tree of each degree
Given a binomial heap of n items, it tell us:

  • The binary digits of n tells us which degree tree are present
  • The longest tree has degree ⌊ log ⁡ 2 n ⌋ \lfloor \log_2n \rfloor log2n and there are at most ⌊ log ⁡ 2 n ⌋ + 1 \lfloor \log_2n \rfloor + 1 log2n+1 trees

Operations

  1. Push a element into the heap
    • put the new element at the beginning
    • Recursively merge two trees of same degree, put the tree with root of smaller key at top, cost O ( 1 ) O(1) O(1) each time
    • cost O ( log ⁡ n ) O(\log n) O(logn) in total, since there are at most ⌊ log ⁡ 2 n ⌋ + 1 \lfloor \log_2n \rfloor + 1 log2n+1 trees
  2. Decrease the key of a element
    • Same as binary heap
    • cost O ( log ⁡ n ) O(\log n) O(logn), since the longest tree has degree ⌊ log ⁡ 2 n ⌋ \lfloor \log_2n \rfloor log2n
  3. Pop the min element
    • scan the tree roots to find the min element, which costs O ( log ⁡ n ) O(\log n) O(logn)
    • delete the root and promote the children, which costs O ( log ⁡ n ) O(\log n) O(logn)
    • merge trees if necessary, which costs O ( log ⁡ n ) O(\log n) O(logn)
  4. Merge two heap
    • cost O ( log ⁡ n ) O(\log n) O(logn)

Comparison

Why not a Linked-List

problem for pop min operation:
no structure maintained to tell us which is the second smallest element

popmin push decrease key merge
binary heap O ( log ⁡ n ) O(\log n) O(logn) O ( log ⁡ n ) O(\log n) O(logn) O ( log ⁡ n ) O(\log n) O(logn) O ( n + m ) O(n+m) O(n+m)
binomial heap O ( log ⁡ n ) O(\log n) O(logn) O ( log ⁡ n ) O(\log n) O(logn)
O ( 1 ) O(1) O(1) amortized
O ( log ⁡ n ) O(\log n) O(logn) O ( log ⁡ n + log ⁡ m ) O(\log n + \log m) O(logn+logm)
linked list O ( n ) O(n) O(n) O ( 1 ) O(1) O(1) O ( 1 ) O(1) O(1) O ( 1 ) O(1) O(1)

Implementation

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ct1ufeJJ-1611309411310)(binomial heap.assets/binomial_heap_implementation.png)]

An Observastion

most of time, the push operation only needs to touch a few small elements, not the big tree of depth Ω ( log ⁡ n ) \Omega(\log n) Ω(logn), which allows it to be O ( 1 ) O(1) O(1) amortized

How to modify decreasekey to achieve a similar benefit?

popmin push decrease key merge
binary heap O ( log ⁡ n ) O(\log n) O(logn) O ( log ⁡ n ) O(\log n) O(logn) O ( log ⁡ n ) O(\log n) O(logn) O ( n + m ) O(n+m) O(n+m)
binomial heap O ( log ⁡ n ) O(\log n) O(logn) O ( log ⁡ n ) O(\log n) O(logn)
O ( 1 ) O(1) O(1) amortized
O ( log ⁡ n ) O(\log n) O(logn) O ( log ⁡ n + log ⁡ m ) O(\log n + \log m) O(logn+logm)
linked list O ( n ) O(n) O(n) O ( 1 ) O(1) O(1) O ( 1 ) O(1) O(1) O ( 1 ) O(1) O(1)
Fibonacci Heap O ( log ⁡ n ) O(\log n) O(logn) amortized O ( 1 ) O(1) O(1) amortized O ( 1 ) O(1) O(1) amortized

A blend of binomial heap and linked list

Fibonacci Heap

Structure & Operations

Basic Operation

consists a list of trees, each tree is a heap, of any shape

  1. push
    • put to the end of the list
    • maintain the min tag
  2. popmin
    • delete the min, O ( 1 ) O(1) O(1)
    • promote the children
    • cleanup: merge every two tree of same degree
    • maintain the min tag

summary:
lazy data structure, just dump the new element, until the next popmin operation

Reason why it’s fast:
do cleanup in batchs

Difficult Operaton: Decrease Key

  • if not heap-violating, done
  • if does, dump the node into the root list

Problem:
we might end up with a heap where the trees have Ω ( n ) \Omega(n) Ω(n) childrens, so popmin would take Ω ( n ) \Omega(n) Ω(n) time

Solution:

  • Rule1: lose one child, and you are marked a LOSER
  • Rule2: lose two child, and you are dumped into the root list
  • Rule3: losing a child becuase of Rule2 is also counted as a “lose”

Summary:
Fibonacci heap nodes have a brutal mechanism to make sure they have enough grandchildren: if your children don’t have enough children of their own, disown them.

What about Increase Key?

You may wonder why there is no increasekey. In fact, it can be implemented using the decreasekey and popmin, and takes O ( log ⁡ n ) O(\log n) O(logn) time as a result. See this answer for details.

Implementation

push

void FbHeap::push(int key, int value)
{
    
    
    Node *cur = new Node(key, value);
    dump_into_list(cur);
    if (min_node == NULL || cur->key < min_node->key) {
    
    
        min_node = cur;
    }
}

pop_min

void FbHeap::pop_min()
{
    
    
    promote_children(min_node);
    remove_node_from_tree(min_node);
    delete min_node;
    clean_up();
}

void FbHeap::promote_children(Node *root)
{
    
    
    Node* nxt; /* backup of cur->next */
    for (Node *cur = root->child; cur != NULL; cur = nxt) {
    
    
        nxt = cur->next;
        dump_into_list(cur);
    }
}

void FbHeap::clean_up()
{
    
    
/* Step 1: For any two trees of same degree, merge them */
    /* use root_arr like a hash table, whose key is degree of node */
    Node **root_arr = new Node*[MAXDEGREE+1];
    memset(root_arr, 0, (MAXDEGREE+1)*sizeof(Node*));
    /* traverse all the node in root list and add them into the hash table */
    /* at the same time, record the max degree */
    int maxD = 0;
    Node *nxt;
    for (Node *root = head->next; root != tail; root = nxt) {
    
    
        nxt = root->next; /* backup next root node */
        Node *cur = root;
        /* whenever previous node has the same degree, merge them */
        while (root_arr[cur->degree] != NULL) {
    
    
            Node *tmp = root_arr[cur->degree];
            root_arr[cur->degree] = NULL;
            cur = merge(cur, tmp);
        }
        root_arr[cur->degree] = cur;
        maxD = max(maxD, cur->degree);
    }
    /* take out nodes in hash table and create a new root list */
    Node *cur = head;
    for (int i = 0; i <= maxD; ++i) {
    
    
        if (root_arr[i] != NULL) {
    
    
            cur->next = root_arr[i];
            cur->next->prev = cur;
            cur = cur->next;
        }
    }
    cur->next = tail;
    tail->prev = cur;   
/* Step 2: update min_node */
    int min_key = INT_MAX;
    min_node = NULL;
    for (cur = head->next; cur != tail; cur = cur->next) {
    
    
        if (cur->key < min_key) {
    
    
            min_key = cur->key;
            min_node = cur;
        }
    }
    /* release memory */
    delete[] root_arr;
}

decrease_key

void FbHeap::decrease_key(Node* cur, int key)
{
    
    
    cur->key = key;
    /* if the new key isn't heap-violating, done */
    if (cur->parent == NULL || cur->key >= cur->parent->key)
        return ;
    /* otherwise, dump the cur into the root list, set the parent's LOSER tag */
    /* if it is already loser, dump it into the root list, and reset LOSER tag */
    Node* parent;
    do {
    
    
        parent = cur->parent; /* backup parent */
        remove_node_from_tree(cur);
        dump_into_list(cur);
        cur->loser = false;
        cur = parent;
    } while (cur && cur->loser);
    /* set the closest non-loser grandparent's LOSER tag */
    if (cur)
        cur->loser = true;
}

Analysis

let N N N be the number of all elements in heap

O ( log ⁡ N ) O(\log N) O(logN) Degree and popmin

call a node of k k k degree “k-node”
call a tree whose root is a k-node “k-tree”

  • A k-node has k k k childs, whose degree ≥ 0 , 0 , 1 , 2 , ⋯   , k − 2 \ge 0,0,1,2,\cdots,k-2 0,0,1,2,,k2 respectively.
    • when a k-node x is linked to k-node y, y becomes (k+1)-node
    • x lose at most one child, which means x is at least a (k-1)-node
  • let F ( k ) F(k) F(k) be the least number of nodes in a k-tree, obviously F ( 0 ) = 1 F(0)=1 F(0)=1, then F ( k ) ≥ F ( 0 ) + F ( 0 ) + F ( 1 ) + ⋯ + F ( k − 2 ) F(k) \ge F(0)+F(0)+F(1)+\cdots + F(k-2) F(k)F(0)+F(0)+F(1)++F(k2)
  • F ( k ) = F ( 0 ) + F ( 0 ) + F ( 1 ) + ⋯ + F ( k − 2 ) ⇒ F ( k + 2 ) = F ( k + 1 ) + F ( k ) ⇒ F ( k )  is Fibonacci Sequence ⇒ F ( k ) = ϕ k − 2 F(k) = F(0)+F(0)+F(1)+\cdots + F(k-2) \\ \Rightarrow F(k+2)=F(k+1)+F(k) \\ \Rightarrow F(k) \ \text{is Fibonacci Sequence} \\ \Rightarrow F(k)=\phi^{k-2} F(k)=F(0)+F(0)+F(1)++F(k2)F(k+2)=F(k+1)+F(k)F(k) is Fibonacci SequenceF(k)=ϕk2, where ϕ = ( 1 + 5 ) / 2 \phi = (1+\sqrt 5) / 2 ϕ=(1+5 )/2
  • So size of k-tree ≥ F ( k ) ≥ ϕ k − 2 ⇒ degree of tree = O ( log ⁡ N ) \text{size of k-tree} \ge F(k) \ge \phi^{k-2} \\ \Rightarrow \text{degree of tree} = O(\log N) size of k-treeF(k)ϕk2degree of tree=O(logN)
  • As a result, the amortized cost of popmin is O ( log ⁡ N ) O(\log N) O(logN)

decreasekey

everytime decrease a key, set a LOSER
so how many times you decrease a key in total, how many times could you reset a LOSER total
so every time decrease a key, reset a chain of LOSER with average length 1.
so the time complexity for decreasekey is 2 = O ( 1 ) 2=O(1) 2=O(1)

猜你喜欢

转载自blog.csdn.net/w112348/article/details/112988821