优先堆也可以用链表

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的上滤操作也很容易实现。

转载于:https://my.oschina.net/chaiko/blog/543268

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值