堆(heap)又名优先队列(priority queue),是一种特殊的树。
1. 堆的两个性质:
① structure property: 是一个完全二叉树;
因此可用数组表示,BT[n+1]的形式,BT[0]为dummy.
② order property: (min heap为例,大小关系变反之后则为max heap)
表述一:每个子树的根的值都比子树中其他值小;
表述二:每个结点的值都比左右子小;
表述三:从根到任何叶的(唯一)路径上,结点的值递增;
2. 堆的父子结点
注:如果要从BT[0]开始保存堆,则以上公式应变为:
parent = ( i - 1 ) / 2 ;
left_child = 2 * i + 1 ;
right_child = 2 * i + 2 ;
3. 堆的基本操作
(1)Insertion
我们采用的方法是
先把将入结点无脑放入最高层右边(先满足structure property)
再对该结点进行percolate up操作(再满足order property)
其实放入最高层右边后,堆只有从根到它的这一条路径的顺序受到了影响,因此只需要用percolate up操作调整这一路的顺序。调整之后,这一条路上的所有结点值一定小于等于调整前的值,且仍保有递增性质,由此可知调整后的堆一定完全满足order property.
void Insert( ElementType X, PriorityQueue H )
{
if ( IsFull( H ) ) { Error( "Priority queue is full" ); return; }
int i;
H->Elements[++H->Size] = X;
// 对暂时位于最末的X进行 percolate up
PercolateUp(H->Size, H);
// for (i = H->Size; H->Elements[i / 2] > X; i /= 2)
// H->Elements[i] = H->Elements[i / 2];
// H->Elements[i] = X;
return;
}
(2)DeleteMin
DeleteMin的本质就是删除根结点,且原则上对于堆的删除只能删除根节点。
我们采用的方法是
先把末尾结点值覆盖给根节点(先满足structure property)
再对根结点进行percolate down操作(再满足order property)
void DeleteMin( PriorityQueue H )
{
if (IsEmpty(H)) { Error( "Priority queue is empty" ); return H->Elements[0]; }
int i, SmallChild;
ElementType X= H->Elements[H->Size--];
H->Elements[1] = X;
// 对暂时位于根处的X进行 percolate down
PercolateDown(1, H);
// for (i = 1; i * 2 <= H->Size; i = SmallChild) { /* 如果不是叶(=有子=有左子)*/
// SmallChild= i * 2;
// if (SmallChild!= H->Size && H->Elements[SmallChild+1] < H->Elements[SmallChild])
// SmallChild++;
// /* 至此SmallChild已为较小的子节点 */
//
// if ( X> H->Elements[SmallChild] )
// H->Elements[i] = H->Elements[SmallChild];
// else break;
// }
// H->Elements[i] = X;
return;
}
(3)Percolate Up
p是被调整元素的位置(其实是被调整元素到根的那条路被调整)。
void PercolateUp(int p, PriorityQueue H)
{
ElementType X = H->Elements[p];
while (X < H->Elements[p / 2]) {
H->Elements[p] = H->Elements[p / 2];
p = p / 2;
}
H->Elements[p] = X;
return;
}
(4)Percolate Down
p是被调整元素的位置(其实是被调整元素不断寻找较小子节点到叶的那条路被调整)。
void PercolateDown(int p, PriorityQueue H)
{
int i, SmallChild;
ElementType X = H->Elements[p];
for (i = p; i * 2 <= H->Size; i = SmallChild) { /* 如果不是叶(=有子=有左子)*/
SmallChild= i * 2;
if (SmallChild != H->Size && H->Elements[SmallChild+1] < H->Elements[SmallChild])
SmallChild++;
/* 至此SmallChild已为较小的子节点 */
if (X > H->Elements[SmallChild])
H->Elements[i] = H->Elements[SmallChild];
else break;
}
H->Elements[i] = X;
return;
}
值得注意的是
① 在percolate过程中,把被调整一路想成一个顺序排列的数组更好;
② 给了一个树,左右子树都是堆,把这个树调整成堆,所需要的的操作,可记作“左右堆变成堆”。
则有:percolate down操作 == “左右堆变成堆”操作。
(5)BuildHeap
给定一堆数,要求建立一个它们的堆。作N次Insertion是很慢的,由以上②引出了一种好方法,即先原序放入数组构成树,之后对每一个非叶子节点进行“左右堆变成堆”操作。 这是一种线性算法。以完美二叉树为例,它的各个节点的height值之和为O(N).
【Theorem】For the perfect binary tree of height h containing 2(h+1) - 1 nodes, the sum of the heights of the nodes is 2 h+1 - 1 - (h + 1).
void BuildHeap(PriorityQueue H)
{
int i;
for(i = H->Size/2; i >= 0; i--){ //H->Size/2是最末的非叶节点 其他非叶节点只需--
PercolateDown(i, H);
}
return;
}