堆具有结构性和堆序性,类似于avl平衡树,对堆的操作可能破坏其中的性质,所以操作必须到所有性质都被满足才终止。
1.使用二叉堆来实现
二叉堆是完全二元树(二叉树)或者是近似完全二元树(二叉树)
见树的概念整理
堆序性:任意节点就应该小于它的所有后裔。(所以最小值总可以在根处找到)
结构性质:二叉堆是一颗被完全填满的二叉树,有可能的例外是在底层,底层的元素从左到右填入
特点:堆顶是最大/小的元素。即所谓的最大堆/最小堆
操作:
插入:使用上滤:这就是把当前节点向上寻找其该在的位置。重点是明白它的要求是必须保证当前节点以及是个最大,最小堆。要不然,如果当前节点和这个节点的全部子节点形成的队列不是个最大,最小堆得话,上滤是没有意义的,因为并没有把真正最大,最小的元素“滤”上去。它最多的应用是插入节点。操作如下:
代码实现:
把对被插入项的引用放到位置0处使循环终止
删除:使用下滤:就是把当前节点向下寻找其应该在的位置,它要求当前节点的左右子节点都满足堆的性质。最多的应用在堆排序的过程中:每次把最大堆的第一个节点(最大)和当前排序队列的最后一个元素交换,这样当前最大值就去了该去的地方,再对当前首元素进行下滤。其次还有删除节点。
删除最小值使用下滤代码实现:
d-堆:d-堆是二叉堆的简单推广,它就像一个二叉堆,只是所有的节点都有d个儿子(所以,二叉堆是2-堆)。
我们的需求是有大量的insert操作,而仅有少量的deleteMin,那d堆从理论上讲就有性能优势了。因为d 远大于2时,树的高度很小啊,但是当d不是2的倍数时,除法操作不能通过移位来实现,也许会有一定的性能损失,这也是为什么insert操作分析中讲的“插入性能会有一定的提高”。
而如果有大量的deleteMin操作,那d堆反而可能会除低性能,因为:d 越大,说明节点的儿子个数越多,找出权值最小的儿子就需要更多的比较次数了。
如3-堆:
左式堆:
与二叉堆唯一区别:左式堆不是理想平衡,二实际上趋向于非常不平衡
左式堆的核心操作是 merge, 无论insert 还是 deleteMin 都是基于 merge操作的;
1)相关定义
-
1.1)零路径长度定义: 到没有两个儿子的节点最短距离, 即零路径长Npl 定义为 从 X 到一个没有两个儿子的 节点的最短路径的长;也即, 非叶子节点到叶子节点的最少边数,其中NULL的零路径长为-1, 叶子节点的零路径长为0;(干货——零路径长的定义—— 非叶子节点到叶子节点的最少边数,非常重要,因为左式堆的定义是基于零路径长的定义的)
-
1.2)左式堆定义:一棵具有堆序性质的二叉树 + 零路径长:左儿子 ≧ 右儿子 + 父节点 = min{儿子} +1;(干货——左式堆的定义是建立在具有堆序性的二叉树上,而不是二叉堆上)
趋向于加深做路径,所以右路径短。即沿左式堆右侧的右路径确实是该堆中最短路径。
堆的合并:
左式堆的merge操作执行后,还要update 左式堆根节点的零路径长, 左式堆根节点的零路径长 == min{儿子的零路径长} +1
update 后, 还需要比较 左右零路径长 是否满足左式堆的定义, 如果不满足,还需要交换左式堆根节点的左右孩子;
不更新零路径长则堆不满足左式性质。时间界将不会生效。
斜堆:
基于左倾堆的概念,也是一个用于快速合并的堆结构,但它可自我调整(self-adjusting),每一个merge操作的平摊成本仍为O(logN),其中N为结点数,而且和左倾堆相比,每个结点没有npl属性,从而节省了空间。斜堆并不能保证左倾,但是每一个合并操作(也是采取右路合并)同时需要无条件交换(而左倾堆中只是根据左右子树的npl值不符合要求时才交换)左右子树,让新合并的右子树变成左子树,原来的左子树变成新的右子树(这有点类似于Splay Tree中的做法),从而可以达到平摊意义上的左倾效果。注意:一颗子树r和NULL子树合并时并不需要交换r子树的左右子树。
由于斜堆并不是严格的左倾堆,最坏的情况下右路长度可能为N,因此采用递归调用merge的风险是出现stack overflow。
二项队列:
二叉堆一样,二项队列也是优先级队列的一种实现方式。
二项队列而言,它可以弥补二叉堆的不足:merge操作的时间复杂度为O(N)。二项队列的merge操作的最坏时间复杂度为O(logN)。
二项队列的合并操作为什么是O(logN)?因为:对于N个结点的二项队列,最多只有logN棵二项树。而合并操作就是合并两棵高度相同的二项树。(合并操作是指将二个二项队列合并,合并这两个二项队列中高度相同的二项树)
一丶二项队列的结构性
二项队列是一群树的集合,是森林,是特殊的森林。这个森林的特殊之处在于组成森林的树都是二项树,下图就是最为典型的二项队列。
图1所示的是一个典型的二项队列,它是由四课树组成的森林,接下里对应图1所示的结构介绍二项队列具有的性质和特点。
(1)二项树,观察图1中的四棵树,若不考虑节点数值的因素,可以看出树2是由两个树1拼凑而成的,其中一个树1结构充当另外一个树1的右子树。观察树3同样也是如此,是一颗树2充当了另外一颗树2的右子树生成而来,同理可见树4。依据上述规律所生成的树都称为二项树,树1,树2,树3,树4均是二项树。根据二项树的生成规律,不难知道高度为i的二项树的结构是固定的,其节点总数为2^(i-1)个。
(2)二项队列从结构上来说是由高度不重复的若干个二项树构成的森林,高度不重复意味着相同高度的二项树在二项队列中最多存在一个。这里特别要注意二项队列中的二项树的高度不强制是连续的,比如高度为2的二项树不存在,高度为5的二项树却存在。
(3)从结构上来研究二项队列,其实二项队列还具有一个特别有意思的性质,若二项队列的总节点数已知道,那么二项队列的结构就是确定的。比如已知二项队列中的总节点树是15,想二项树的节点数,只有一种组合8+4+2+1,二项队列结构如下。
其实也很好理解,二项队列总节点数15,表示为二进制就是1111,二项树的节点数正好符合二进制上每位的权值。
二丶二项队列的“堆序性”
假如一个森林,仅仅满足了上述的结构性,那它也还不是二项队列,毕竟二项队列是优先队列的一种实现,无论如何也是需要具备和二叉堆和左式堆一样的堆序性质的。同样借助图1来介绍二项队列所具备的”堆序性质”。
(1)组成二项队列的所有二项树都具有一致的堆序性,每一个二项树不仅仅满足堆序性,而且每个二项树的堆序性必须保持一致,要么都是大根堆,要么就是小根堆。
左式堆递归威力完美,斜堆是缺少平衡原则的一种重要数据结构。二项队列指出了一个简单的想法如何能够用来达到好的时间界。
见下篇