排序大集锦(二):N叉堆排序及优先级队列

首先需要明白何谓“堆”。逻辑上它是一种特殊的树形结构,而其物理实现仅仅是一维数组,如下所示:


上图中节点中的数表示元素的索引,将索引从1开始而不是0,可以使得整棵树从当前节点获得其孩子节点的操作对于根节点来说也是适用的,而额外“浪费”的这个空间可以用做哨兵使得比较操作的次数更少。可简单得出两点结论:
①除顶层与底层之外,每一层的节点数与上层节点数的比值固定。
②最底层的叶子节点尽可能向左分布。
注意在任意一种树形结构中并不一定保证同时满足上述两种性质,它仅仅适用于堆这种特殊的树,而往往也由于上述两种性质的成立导致在该数据结构中实施插入和删除操作时,在极为高效的前提下同样也能保证高效的空间利用。由结论①可知,在具有固定元素个数的集合中,如果我们改变每一层与上层节点数之间的比值,一般来说将存在如下关系:即当比值越大则堆的层数越小,因此从树的顶层到达底层将花费更少的时间。由结论②可知,因为叶子节点尽可能向左分布,因此在一维数组中内节点与叶子节点之间存在明确的边界。


根据上述分析,在给定一个节点的索引i之后,可以得到该节点的父、子节点。

/*make the number of node's sons generalization*/
declare s as the number of node's sons
PARENT(i)  /*current node's parent*/
return (i+s-2)/s
//------------------------------------------------------
FARLEFT(i) /*far left child*/ 
return ((i-1)*s+2)
//------------------------------------------------------
FARRIGHT(i)  /*far right child*/
return (i*s+1)
//------------------------------------------------------
INTERNODE(length[Arr])
return (length[Arr]+s-2)/s

证明过程省略,事实上观察到节点i的s倍为其所有孩子节点中的倒数第二个节点即可完成证明。而上述INTERNODE(length[Arr])子过程,则是根据当前数组的长度计算出内节点的最大索引项。
除此之外最重要的一点是,只有满足在任意节点中存储的关键字大于/小于其孩子节点中的关键字的数据结构方能称之为“堆”


节点中存储的关键字大于其孩子节点中的关键字的堆称为大堆,反之则称为小堆。由于大堆及小堆的构造方式相同,所以我们以大堆为例剖析其构造过程。考虑一个简单集合A={2,1,3},若需要将其变成大堆,只需将1和3简单交换即可,图示如下:


由上述操作可以发现如下事实:即父、子节点形成一个独立的组,该组中的最大值位于父节点处,而兄弟节点之间则不具备明显的关系。由此可以从堆的最后一个节点开始依次向前执行上述操作,最终在索引[1]处将得到整个集合的最大值。注意,当某对父子节点发生交换操作时,有可能使得交换后的子节点与其孩子节点之间的“堆性质”遭到破坏,因此需要使下层节点重新保持堆性质。

HEAPIFY(A,i,n)  /*n appointed to length[A] usually in the procedure of <BUILDHEAP>*/
temp←A[i]
maxnode←0
while TRUE
do farleft←FARLEFT(i)
   farright←FARRIGHT(i)
   if farleft > n
     then break
   if farright > n  /*the number of the last internal node's children maybe less than n*/
     then farright←n
   t←farleft
   for j←farleft+1 to farright  /*get the node has the max value*/
     do t←max(t , j)
   if A[t] > temp
     then A[i]←A[t]
          maxnode←i←t
     else break
if maxnode≠0
  then A[maxnode]←temp
//---------------------------------------------------------------------------
max(a,b)
return ( A[a]>A[b] ? a : b )

假定单步操作的时间开销为O(1),因为while循环的一次迭代过程所执行的比较次数与节点的孩子数相关,因此一次迭代操作所用时间为O(s)(s为节点的孩子数),并且该过程在最坏情况下将从根节点下降至堆的叶子节点处,此时执行㏒n次迭代,运行时间为O(s㏒n)。

从最后一个内节点到根节点,对每个节点使用以上子过程,使得堆中的任意一对父子节点均满足“堆性质”便完成堆的构造:

BUILDHEAP(A)
start←INTERNODE(length[A])
end←1
for i←start to end
  do HEAPIFY(A,i,length[A])

根据上述过程,简单分析其运行时间如下:



堆排序
整个集合最大的元素位于整个堆的根节点处。由于按照升序排序,整个集合的最大元素应位于最后一个节点处,所以交换索引[1]与索引[n]处的元素之后,最大元素已经处于正确的位置,而发生交换操作后的根节点的“堆性质”遭到破坏,因此需要使其重新具备“堆性质”,这一过程是通过HEAPIFY完成。接着重复上述操作直至索引[2]处的节点处,便完成了整个数组的排序操作:

HEAPSORT(A)
BUILDHEAP(A)
for i←length[A] to 2
  do swap(A[1] , A[i])
     HEAPIFY(A,1,i-1)

简单分析其运行时间:由于BUILDHEAP(A)的时间代价为O(n)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值