您的位置:首页 > 其它

优先堆也可以用链表

2015-12-12 00:00 246 查看
摘要: priority heap通常都是数组实现的,本文尝试用链表实现。没有具体代码,有了算法思想很容易实现的,我就不献丑了。

priority heap通常是以数组来实现的,用数组下标作为堆的序号来实现heap。但是在C语言中数组是不能改变大小的(新标准里可能可以改变哈),于是便不能动态地增加堆的大小。

于是想到链表动态增加的特•。链表实现优先堆的难点在于,堆序的乘2除2操作要求能即时获得数组下标,而链表不能随机存取只能顺序存取,对于DeleteMin操作,getMin很容易,只要保存了Child和Parent节点的指针,上滤操作也没什么难度。但是Insert操作就呵呵了,因为并不知道当前堆的末尾在哪,遍历寻至末尾的话,复杂度又上升至O(N^2)。BuildHeap更不用说。

先看看堆树的特点

1

2 3

4 5 6 7

8 9 10 11 12 13 14 15

...

显然,对于堆序而言,序号确定了,在堆树中的位置也确定了。由此可见,如果知道当前堆的元素个数,那么最后一个元素的位置也就确定了。但是怎样找到这个位置呢?我在火车上睡不着,想出了这个方法。。。

先将序号转化成二进制:

1

10 | 11

100 101 | 110 111

1000 1001 1010 1011 | 1100...

...

用|来分隔树的左右两边。

观察后可以得出,位于左半部分的最高两位都是10,位于右半部分的最高两位都是11。

这是显然的,除开根的序号,对于堆树的每一层而言,左边部分的第一个,都是2、4、8、16...2^D。右边部分第一个都是3、6、12... 2^D+2^(D-1),也就是左边部分的序号加上这一层的节点个数的一半,就处于右边部分了:2+1,4+2,8+4,16+8...,换成二进制就是10XXX...+01XXX...=11XXX...,这就是前面观察的结论了。

首先要想到堆树是个二叉树,从根开始,不断地向左子树或者右子树寻找,便可以最终到达树底下,中途无非就是个判断是左还是右的问题。联系上面的观察结果,算法就很明朗了:

确定一个数处于堆树中的哪个位置,只要先把这个数用二进制表示,不断左移,取溢出位,从遇到的第一个1开始(第一个1表示遇到根了),遇到一个0便转向左子树,遇到一个1便转向右子树。简言之就是遇0则左,遇1则右。这个算法具有递归性,对子树又实施本算法,当到达叶子节点时,这个数便左移完了,这时也正好到达这个数在堆树中的位置。

所以,如果知道了堆的大小,便知道了如何从根走到末尾的位置,也就知道了新元素要Insert的位置。显然,这个算法的复杂度是```
这里输入代码
NlogN的。代价是需要两个指针变量来指向LeftChild和RightChild,以及还要维持一个Parent指针,用来实现插入之后的上滤操作。

所以堆树节点的结构体可以是:

struct Node
{
ElementType Element;
Node * LeftChild;
Node * RightChild;
Node * Parent;
}Node;

因为堆树的位置是相对固定的,所以在进行Insert后的上滤操作和DeleteMin后的下滤操作时,在需要交换的地方,只交换Element就可以了,不必交换Node指针。可以想象成堆树就是一个框,元素就是放在框中的东西,交换时只交换东西而不必交换整个框。数组和链表的两种实现,不同之处在于数组实现的优先堆是用数组下标作为框,链表实现的优先堆是用结构体作为框而已。

实际上,数组实现的优先堆中,乘二和除二操作正是左移和右移运算。

实现了Insert操作,BuildHeap可以用Insert来间接实现,从一个空的堆树开始逐个Insert就可以了。有了LeftChild和RightChild以及Parent,DeleteMin的上滤操作也很容易实现。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息