在软件设计相关领域,"堆(Heap)"的概念主要涉及到两个方面:
- 一种数据结构, 逻辑上是一颗完全二叉树,存储上是一个数组对象(二叉堆).
- 垃圾收集存储区,是软件系统可以编程的内存区域.
本文所说的堆,指的是前者.另外, 这篇文章中堆中元素的值, 均以整型为例.
堆排序的时间复杂度是O(nlgN),与快速排序达到相同的时间复杂度. 但是在实际应用中,我们往往采用快速排序而不是堆排序. 这是因为快速排序的一个好的实现,往往比堆排序具有更好的表现. 堆排序的主要用途,是在形成和处理优先级队列方面. 另外, 如果计算要求是类优先级队列(比如, 只要返回最大或者最小元素, 只有有限的插入要求等), 堆同样是很适合的数据结构.
- 基础知识
堆一般用数组表示,比如数组A. 数组的长度Length(A), 堆在数组中的元素个数HeapSize(A). 一般说来, HeapSize(A) <= Length(A), 因为数组A当中可能有一些元素不在堆中.
假设节点I是数组A中下标为i的节点.
- Parent(i) : return Floor(i/2); //I的父节点下标.Floor(i)表示比i小的最大整数.
- Left(i) : return 2*i; //I的左子节点
- Right(i) : return 2*i+1; //I的右子节点
含有n个元素的堆A的高度是: Floor(lgn)
- 堆的基本操作
- MaxHeapify( A, i ):
保持堆的性质.假设数组A和下标i, 假定以Left(i)和Right(i)为根结点的左右两棵子树都已经是最大堆, 节点i的值可能小于其子节点. 调整节点i的位置.
算法的C#实现:
public void MaxHeapify( int[] A, int heapSize, int i )
{
int left = 2*i;
int right = 2*i+1;
int larger = i;
//compare with left child
if ( left <= heapSize && A[left] > A[i])
{
larger = left;
}
//compare larger node with right child
if ( right <= heapSize && A[right] > A[i])
{
larger = right;
}
//exchange i with the larger child
if(larger != i )
{
int tmp = A[i];
A[i] = A[larger];
A[larger] = tmp;
MaxHeapfy( A, heapSize, larger );
}
}
时间复杂度与树的高度成正比, 为 O(lgn).
- BuildMaxHeap( A ):
从一个给定的数组建立最大堆.子数组A[ floor(n/2)+1 .... ... n]中的元素都是树的叶节点(完全二叉树的基本性质) . 从索引 ceiling(n/2)开始一直到1, 对每一个元素都执行MaxHeapify, 最终得到一个最大堆.
算法的C#实现:
public void BuildMaxHeap( int[] A )
{
int heapSize = A.Length;
for( int i = Math.Ceiling(heapSize/2); i >= 1; i --)
{
MaxHeapify( A, heapSize, i );
}
}
时间复杂度依赖于MaxHeapify O(h) (h - 树的高度) ,BuildMaxHeap(A)的时间复杂度是O(n).
- 堆排序 HeapSort( A ):
堆排序算法的基本思想是, 将数组A创建为一个最大堆, 然后交换堆的根(最大元素)和最后一个叶节点x, 将x从堆中去掉形成新的堆A1, 然后重复以上动作,直到堆中只有一个节点.
算法的C#实现:
public void HeapSort( int[] A )
{
BuildMaxHeap(A);
int HeapSize = A.Length;
for(int i = HeapSize; i > 1; i--)
{
//assume the type of A is int[] for example
int tmp = A[1];
A[1] = A[i];
A[i] = A[1];
HeapSize -= 1; //cut off the sorted node.
MaxHeapify( A, HeapSize, 1 ); // sort the A1 to a max heap
}
}
堆排序的时间复杂度是O(nlgn).
- 优先级队列算法-增加某元素的值(优先级) : HeapIncreaseKey( A, i, key )
增加某一个元素的优先级后(元素的值), 该元素应该向上移动,才能保持堆的性质.
算法的C#实现:
public void HeapIncreaseKey( int[] A, int index, int key )
{
if( key <= A[index])
return;
A[index] = key;
while( index>1 && A[Parent(index)] < A[index]]
{
//prompt child node
int tmp = A[index]:
A[index]= A[Parent(index)];
A[Parent(index)] = tmp;
index = Parent(index);
}
}
时间复杂度: O(lgn)
- 优先级队列算法-插入一个元素: Insert( S, x ) 将x元素插入到优先级队列S中.
主要思路是, 将堆的最后一个叶节点之后, 扩展一个为无穷小的新叶节点, 然后增大它的值为x的值
算法的C#实现是:
public void HeapInsert( int[] A, int key )
{
A.Length += 1;
A[A.Length] = int.MinValue;
HeapIncreasekey( A, A.Length, key );
}
时间复杂度O(lgn)