Prerequisite: Binomial Heap
Binomial tree of degree k k k
Definition: two k − 1 k-1 k−1 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 k−1,k−2,⋯,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
- 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
- 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⌋
- 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)
- 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
- push
- put to the end of the list
- maintain the min tag
- 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,⋯,k−2 respectively.
- when a k-node
x
is linked to k-nodey
,y
becomes (k+1)-node - x lose at most one child, which means x is at least a (k-1)-node
- when a k-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(k−2)
- 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(k−2)⇒F(k+2)=F(k+1)+F(k)⇒F(k) is Fibonacci Sequence⇒F(k)=ϕk−2, 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-tree≥F(k)≥ϕk−2⇒degree 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)