笔试算法题(46):简介 - 二叉堆 & 二项树 & 二项堆 & 斐波那契堆
二叉堆(Binary Heap)
- 二叉堆是完全二叉树(或者近似完全二叉树);其满足堆的特性:父节点的值>=(<=)任何一个子节点的键值,并且每个左子树或者右子树都是一 个二叉堆(最小堆或者最大堆);一般使用数组构建二叉堆,对于array[i]而言,其左子节点为array[2*i],其右子节点为 array[2*i+1];二叉堆支持插入,删除,查找最大(最小)键值的操作,但是合并二叉堆的复杂度较高,时间复杂度为O(N);但是二项堆或者斐波 那契堆则仅需要O(logN);
二项树(Binomial Tree)
-
定义度数为二项树根节点的直接子节点个数;如果一棵二项树的度数为0,则其只包含一个根节点;如果一棵二项树(包括子树)的度数为K,则其根节点包含K个子节点,并且其子节点分别为度数是K-1,K-2,K-3,…,1,0的子树的根;
-
每当一棵二项树的度数从k-1变成k,则其所有子节点的个数增加2k-1。因此度数为K的二项树的所有子节点个数为1+2+…+2k-1=2k;
-
二项树的高度由其增加的度数锁带来的子树的高度确定(度数每增加1,相当于二项树根节点增加一个其自身大小的子树,所以其高度和节点数都变成2N或者2H),所以其高度为H=k;
-
在深度为h的层(从0开始记),节点个数为C(k, h),也就是从k个数中选h个数的选择方法数;C(k, h)=k!/(h!*(k-h)!);
二项堆(Binomial Heap)
-
二项堆是一种类似于二叉堆的结构,与其相比二项堆的优势在于可以快速合并两个二项堆;二项堆H由一组二项树组成,并且满足下述性质:
-
H中每一棵二项树都满足最小堆性质(父节点的键值小于等于其子节点的键值);此性质保证每棵二项树的根节点都包含最小关键字;
-
H中不能有两棵或者以上的二项树具有相同的度数(包括0);此性质保证节点数为N的二项堆最多只有logN+1棵二项树(也就是按照1,2,22,23,…,2N排序的一组二项树);
-
二项树的合并操作:如果合并两棵度数相同的二项树,只需比较两棵树根节点的大小,选取关键字较小的根节点所在的二项树作为主树,将剩下的一棵树作为主树根 节点的一个子节点(由于新二项树的度数增加了1,则节点数需要增加2k,则新加入的树正好满足节点数增加的需求);时间复杂度为O(1);
-
二项堆的合并操作:给定两个二项堆A和B,分别将A和B各自的二项树按照度数大小排序,[1,2,…,ak]和[1,2,…,bk];按照度数从小到大进行合并,如果度数i位置上,A和B集合中都有对应的二项树,则在O(1)时间内完成合并,并变成度数为i+1的二项树,然后如同加法操作的进位一样参与i+1位置上二项树的合并;
-
如果度数i位置上,A和B集合中仅有一边有对应的二项树,则直接将这唯一的二项树放入结果集合中度数为i的位置(由于合并操作是按照度数从小到大进行,所以当前位的结果不会对之后的操作具有影响),对于具有合计节点为N的A和B的二项堆合并操作而言,其最多具有logN次O(1)时间复杂度的合并操作,所以总计的时间复杂度为O(logN);实际操 作里面首先按照归并方式将两个二项堆的根节点组成的链表合并成为一个链表,这样就有很多度数相同的根节点,接下来的任务就是合并这些根节点度数相同的树;
-
插入操作:无论是对于单个节点的插入,还是对于一棵二项树的插入操作而言,实际上都是一次两个二项堆的合并操作,所以时间复杂度为O(logN);对于单 个节点的插入而言,如果没有时效性要求则可以维护一个新增节点的临时二项树(类似cache),然后一次性与原始的二项堆进行合并;
-
查找最小关键字操作:对于一个二项堆而言,实现的时候使用链表或者数组存储各个二项树的根节点,由于二项树遵循最小堆性质,所以根节点键值就是各个二项树 内的最小值,所以直接查询二项堆维护的链表或者数组即可,时间复杂度为O(logN);(如果根节点维护有序,则具有更好的时效性);
-
删除最小关键字操作:首先在O(logN)时间内找到最小关键字所在的二项树;然后将此二项树从二项堆内删除,并且获取其根节点的所有子树构成一个二项堆 (需要反转其子女的sibling指针,从而构成一个有序的二项堆),所以删除最小关键字操作最终页转换为合并两个二项堆的操作,时间复杂度仍旧为 O(logN);
-
减小某一个节点的关键值操作:递归与其父节点进行交换知道满足最小堆的性质,时间复杂度为O(logN);
-
删除某一个节点的操作:首先将指定节点的关键值设置为无穷小,然后将其提升到所在二项树的根节点处,这样就转换为删除最小关键字的操作,时间复杂度为O(logN);
-
二项堆的实现:(为了便于理解:一棵包含了13个节点的二项堆,其二项树组成为1101,也就是13的二进制表示)
1 struct Node { 2 int key; 3 /** 4 * 当前节点作为根节点的二项树的度数 5 * */ 6 int degree; 7 /** 8 * 如果是根节点,则sibling用作二项堆中串联二项树的指针 9 * 如果不是根节点,则sibling用作同一个父节点下的所有子节点 10 * */ 11 Node *sibling; 12 /** 13 * 用于访问当前节点的所有子节点,此指针仅指向其子节点最左边的一个 14 * 需要在LeftMostChild节点上使用sibling才能访问其他子节点 15 * */ 16 Node *LeftMostChild; 17 /** 18 * 指向父亲节点的回溯指针 19 * */ 20 Node *parent; 21 };
斐波那契堆(Fibonacci Heap)
-
一种松散的二项堆,如果不对FH做任何Decrease-Key或者Delete操作的话,FH中每棵树就跟二项树一样,但跟二项堆的不同点在于如果进行 了上述操作,则构成FH的树可以不是二项树,并且多个树的根节点可以不用排序;FH在优势在于其建堆,插入,获取最小值,合并FH等操作都能在O(1)的 平摊运行时间内完成(删除元素操作除外);FH的缺点在于其常数因子较大,并且程序实现较为复杂,所以实际应用多以二项堆为首选;
-
FH的策略是将堆维护工作尽量推后执行;比如当合并两个FH的时候,并不是立即执行合并操作去保证堆内的树根有序或者没有相同度数的二项树,而是留待到执行Extract-Min操作的时候才执行相同度数二项树的合并;
-
FH中由二项树根节点组成的表示一个双向循环链表,并且同一个父节点下的所有节点也构成一个双向循环链表,其他的构造与二项堆相同;
参考连接:
https://github.com/brandenburg/binomial-heaps/blob/master/iheap.h
http://www.cnblogs.com/xuqiang/archive/2011/06/01/2065549.html
http://en.wikipedia.org/wiki/Fibonacci_heap
http://mindlee.net/2011/09/29/fibonacci-heaps/