简介
设计一种堆结构像二叉堆那样高效的支持合并操作而且只使用一个数组似乎很困难。原因在于,合并似乎需要把一个数组拷贝到另一个数组中去,对于相同大小的堆,这将花费O(N)。正因为如此,所有支持高效合并的高级数据结构都需要使用指针。
像二叉堆那样,左式堆也有结构性和堆序性。不仅如此,左式堆也是二叉树,它和二叉堆之间的唯一区别在于:左式堆不是理想平衡的,而实际上是趋向于非常不平衡。
左式堆性质
把任意节点X的零路径长(null path length, NPL)Npl(X)定义为从X到一个没有两个儿子的节点的最短路径长。因此,具有0个或1个儿子的节点的Npl值为0,而Npl(NULL)=-1。注意,任意节点的零路径长比它的各个儿子节点的最小值多1。
左式堆的性质是:对于堆中的每一个节点X,左儿子的零路径长至少与右儿子的零路径长一样大。因此,下图1中,左边的二叉树是左式堆,而右边的二叉树则不是。这个性质使左式堆明显更偏重于使树向左增加深度,左式堆的名称也由此而来。
图1:两棵树的零路径长,只有左边的树是左式堆
左式堆操作
左式堆的基本操作是合并(Merge),注意,插入只是合并的特殊情形。因为可以把插入看成是单节点堆与一个大的堆的Merge。对于合并操作,主要采取的是递归解法。如图2所示,两个左式堆H1,H2,注意,最小元在根处。除数据,左指针,右指针外,每个单元还需要有一个指示零路径长的项。
图2:两个左式堆H1,H2
如果这两个堆有一个是空的,那么可以直接返回。否则,为了合并两个堆,需要比较它们的根。首先,将具有大的根值的堆与具有较小的根值的堆的右子树堆合并。在本例中,我们递归的将H2与H1在8处的右子树堆合并,得到如图3中的堆。
图3:将H2与H1的右子树堆合并的结果
如果直接将图3中的堆作为H1的右儿子,如图4所示,那么新的H1虽然满足堆序性质,但是它不是左式堆,因为左子树的零路径长为1,而根的右儿子的零路径长为2.因此,左式堆的性质在根处被破坏。不过,很容易看到,树的其余部分必然是左式堆,由于递归步骤,根的右子树也是左式堆。根的左子树没发生变化,必然也是左式堆。这样一来,只需要对根进行调整就可以了。使整个树是左式堆的做法如下:只要交换根的左儿子和右儿子,并更新零路径长,就完成了Merge。如图5所示:
图4:H1接上图3中的左式堆作为右儿子的结果
图5:交换H1的根的儿子得到的结果
上面提到,我们可以通过把被插入项看成单节点并执行一次Merge来完成插入。为了执行DeleteMin,只要除掉根而得到两个堆,然后再将这两个堆合并。因此执行一次DeleteMin操作的时间为O(logN)。
代码实现
头文件:leftheap.h
typedef int ElementType;
#ifndef _LeftHeap_H
#define _LeftHeap_H
struct TreeNode;
typedef struct TreeNode *PriorityQueue;
PriorityQueue Initialize( void );
ElementType FindMin( PriorityQueue H );
int IsEmpty( PriorityQueue H );
PriorityQueue Merge( PriorityQueue H1, PriorityQueue H2 );
#define Insert( X, H ) ( H = Insert1( (X), H))
PriorityQueue Insert1( ElementType X, PriorityQueue H );
PriorityQueue DeleteMin( PriorityQueue H );
#endif
源文件:leftheap.c
#include "leftheap.h"
#include <stdio.h>
#include <stdlib.h>
struct TreeNode
{
ElementType Element;
PriorityQueue Left;
PriorityQueue Right;
int Npl;
};
PriorityQueue Initialize( void )
{
return NULL;
}
int IsEmpty( PriorityQueue H )
{
return H == NULL;
}
ElementType FindMin( PriorityQueue H )
{
if (IsEmpty(H)){
printf("Priority queue is empty!\n");
return -1;
}
return H->Element;
}
void SwapChildren( PriorityQueue H )
{
PriorityQueue tmp;
tmp = H->Left;
H->Left = H->Right;
H->Right = tmp;
}
//静态函数,只在该源文件内可见
static PriorityQueue Merge1( PriorityQueue H1, PriorityQueue H2 );
PriorityQueue Merge( PriorityQueue H1, PriorityQueue H2 )
{
if (H1 == NULL)
return H2;
if (H2 == NULL)
return H1;
if (H1->Element < H2->Element)
return Merge1(H1, H2);
else
return Merge1(H2, H1);
}
static PriorityQueue Merge1( PriorityQueue H1, PriorityQueue H2 )
{
if (H1->Left == NULL) //因为是左式堆,若左儿子为null,那么右儿子必然也为null
H1->Left = H2;
else{ //H1是根值较小的堆,故递归合并它的右子树与H2;
H1->Right = Merge(H1->Right, H2);
if (H1->Left->Npl < H1->Right->Npl)
SwapChildren(H1);
H1->Npl = H1->Right->Npl + 1;
}
return H1;
}
PriorityQueue Insert1( ElementType X, PriorityQueue H )
{
PriorityQueue SingleNode;
SingleNode = malloc(sizeof(struct TreeNode));
if (SingleNode == NULL){
printf("Out of space!");
}
else{
SingleNode->Element = X;
SingleNode->Npl = 0;
SingleNode->Left = SingleNode->Right = NULL;
H = Merge(SingleNode, H);
}
return H;
}
PriorityQueue DeleteMin( PriorityQueue H )
{
PriorityQueue LeftHeap, RightHeap;
if (IsEmpty(H)){
printf("Priority queue is empty!");
return H;
}
LeftHeap = H->Left;
RightHeap = H->Right;
free(H);
return Merge(LeftHeap, RightHeap);
}