二项堆-原理及伪代码
二项堆
(图摘自:http://blog.csdn.net/acceptedxukai/article/details/6951922)
一、介绍
一颗二项堆是由一组二项树组成。
二项树是一种递归的定义:
- 二项树B[0]仅仅包含一个节点
- B[k]是由两棵B[k-1]二项树组成的,其中一颗树是另一颗树的子树,下面是B0-B4二项树:
图:二项树
二项树的性质:
- 对于二项树B[k]有2^k个节点
- 在深度为i的层中,有才C(k,i)个节点,其中i为0,1,2...k
- 根节点的度数为k,大于树中任何其它节点的度数
- 一颗包含n个节点的二项树,任意节点的最大度数为lgn
二项堆H是由一组满足下面条件的二项树组成的:
- H中的每个二项树遵循最小堆性质
- 对于非负整数k,每个二项堆里最多有一棵度数为k的二项树
每个拥有n个节点的二项堆里面,一共有[lgn]+1颗二项树,将n转换为二进制,第i位上为1对应有一颗二项树B[i]存在于二项堆中,如n为13,二进制为1101,那么这个二项堆有B[3],B[2]和B[0]三颗二项树,如下图a
图:含有13个节点的二项堆
上图中的b,表示了二项堆的存储结构。
二、操作
各种结构体定义
二项堆中的每颗二项树都按照做孩子右兄弟的方式进行存储。每个节点包含parent(父节点)、child(左孩子)、sibling(右兄弟)、degree(度数)和value(值)。
struct heap_node{ struct heap_node *parent; struct heap_node *child; struct heap_node *sibling; unsigned int degree; void *value; };
二项堆的结构体定义:
struct bino_heap { struct heap_node *head; struct heap_node *min; };
合并(Merge)操作
合并是指合并两个二项堆,时间复杂度O(lgn)。
主要分为两个步骤:
(1) 堆合并:将两个堆中的树以根节点的度数进行升序排列,形成一个新的二项堆,新二项堆以度数最小的二项树的根节点作为堆的头节点,以链表形式将所有的二项树的根节点链接起来。
图:二项堆合并
(2) 二项树合并:遍历根节点x时:
Case1:degree[x] != degree[sibling[x]],将指针指向下一个根节点,到Case1,否则到Case2
Case2:当x为具有相同度数的三个根中的第一个时,degree[x]==degree[sibling[x]]==degree[sibling[sibling[x]]]时,则指针指向下一个根节点,进入Case1,否则进入Case3或者Case4
Case3:head[x]小于等于head[sibling[x]],则将x作为合并后的二项根,将x和sibling[x]进行树合并
Case4:head[x]大于head[sibling[x]],则将sibling[x]作为合并后的二项根,将x和sibling[x]进行树合并
图:二项树合并
树合并:对相同度数的二项树进行合并,找出其中根节点较小的二项树A作为父,将将另一颗二项树B的根节点的右兄弟指向A的左子,并B作为A根节点的左子。
伪代码:
bino_tree_merge(p, c) parent[c] <- p sibling[c] <- child[p] child[p] <- c return p bino_heap_merge(H1, H2) node1 <- head[H1] node2 <- head[H2] while node1 != NULL and node2 != NULL: if degree[node1] <= degree[node2] H3 <- node1 node1 <- sibling[node1] else H3 <- node2 node2 <- sibling[node2] if pre = NULL pre <- H3 head[heap] <- H3 else sibling[pre] <- H3 pre <- H3 if node1 != NULL sibling[H3] <- node1 else sibling[H3] <- node2 return heap bino_heap_union(H) H <- bino_heap_merge(H1, H2) x <- head[H] pre <- NULL while sibling[x] != NULL: if degree[x] != degree[sibling[x]]: pre <- x x <- sibling[x] else if sibling[sibling[x]] != NULL: if degree[x] = degree[sibling[sibling[x]]] pre <- x x <- sibling[x] else: x <- bino_tree_merge(x, sibling[x]) if pre != NULL sibling[pre] <- x else heap <- x else: x <- bino_tree_merge(x, sibling[x]) if pre != NULL sibling[pre] <- x else heap <- x return heap
查找最小值
这个比较简单,由于二项堆中的每个二项树都满足最小堆性质,所以只要遍历所有的根节点就能获取到最小值。复杂度O(lgn)。
bino_heap_min(H) min_node <- head[H] tmp <- sibling[min_node] while tmp != NULL if value[tmp] < value[min_node] min_node <- tmp tmp < sibling[tmp] return min_node
查找节点
查找节点需要根据二项堆的每颗二项树都满足最小堆的性质这条原则,即如果当前节点的value大于查找的value,则不再查询当期节点的子节点,而转向当前节点的右兄弟,如果右兄弟为空,则转向查询当前节点的父节点的右兄弟。
图:查找17的过程
bino_heap_search(H, v) node <- head[H] while node != NULL if value[node] < v and child[node] != NULL node <- child[node] else if value[node] > v or child[node] = NULL while sibling[node] = NULL node <- parent[node] if node = NULL return NULL node <- sibling[node] else return node return NULL
插入节点
插入节点相当于先创建一个单节点二项堆H’,然后将H’与原有的二项堆H进行合并。复杂度O(lgn)。
bino_heap_insert(H, n) value[newHeap] <- n heap <- head[H] if heap = NULL: heap <- newHeap else: heap <- bino_heap_union(heap, newHeap) return heap
删除节点
删除节点也可以分为两步,将要删除的节点的值减小的最小,并恢复二项堆的性质,然后将二项堆的最小值得节点删除。复杂度O(lgn)。
改变节点的值并恢复二项堆性质(复杂度O(lgn)):
bino_heap_descrease(H, x, k) if k > value[x]: return value[x] <- k y <- x z <- parent[y] while z != NULL and value[y] < value[z]: tmp <- value[y] value[y] <- value[z] value[z] <- tmp y <- z z <- parent[y]
删除最小节点(复杂度O(lgn)):
最小节点肯定为二项堆H中某个二项树T的根,删除该节点的步骤分为两个:
- 将拥有该最小节点的二项树T从二项堆H中拿出来,删除根节点后逆序排列剩下的二项树,形成一个新的二项堆newH,原来的二项堆H删除T
- 将newH和H进行二项堆合并,形成新的二项堆
bino_heap_remove_min(H) min_node <- bino_heap_min(H) node <- head[H] while sibling[node] != NULL and sibling[node] != min_node: node <- sibling[node] sibling[node] <- sibling[sibling[node]] new_node <- child[min_node] next_node <- NULL while new_node: head[newH] <- new_node parent[new_node] <- NULL tmp_node <- sibling[new_node] sibling[new_node] <- next_node next_node <- new_node new_node <- tmp_node return bino_heap_union(H, newH)
删除节点的整个流程(O(lgn)+O(lgn)=O(lgn)):
先找出要删除值得节点,如果没有,则返回原有二项堆,如果有,则先将对应节点的值减小的最小值,然后恢复二项堆性质,最后再删除该二项堆的最小节点。
bino_heap_remove_node(H, v) node <- bino_heap_search(H, v) if node = NULL return H bino_heap_descrease(H, node, 0xFFFFFFFF) newH <- bino_heap_remove_min(H) return newH