二叉堆(也叫堆)是一个部分排序的二叉树,其排序规则体现在它的堆序性质上:最大堆和最小堆,最大堆就是其对于任一节点,每个节点的键值都大于等于它的孩子节点,所以根节点键值最大。最小堆则相反。
堆是一棵完全二叉树,具备完全二叉树的性质,可以用一个数组表示而不需要指针,在起始位置为 0 的数组中任一位置 i 上的元素,其左儿子在位置 2*1+1 上,右儿子在左儿子的后面邻近位置上,它的父节点则在位置 (i-1)/2。因此,一个堆数据结构将由一个数组(不管键值是什么类型)、一个代表最大容量的整数以及当前的堆大小组成。
下面用C++ 来实现堆,并补充基于堆这一数据结构上的堆排序,不同于前面介绍的堆排序
1. 堆的类结构
template <class Elem>
class BinaryHeap
{
public:
BinaryHeap(int MaxSize = 50);
BinaryHeap(const BinaryHeap<Elem> &rhs);
BinaryHeap(Elem *Array, int ElemNum, int MaxSize);
~BinaryHeap(void);
Elem *Sort(void); //堆排序
bool Add(const Elem &Item); //添加元素
Elem Remove(void); //移除(堆顶)元素
inline int GetSize(void); //获取当前堆大小
protected:
Elem *Data;
int CurrentNum;
const int MAX_SIZE;
void HeapifyUp(int Node); //向上调整堆
void HeapifyDown(int Node); //向下调整堆
inline int ParentOf(int Node); //得到节点的父节点位置
inline int LeftChildOf(int Node); //得到节点的左儿子位置
};
2. 构造函数,拷贝构造函数,析构函数
//constructor function
template <class Elem>
BinaryHeap<Elem>::BinaryHeap(int MaxSize) :MAX_SIZE(MaxSize)
{
Data = new Elem[MAX_SIZE];
CurrentNum = 0;
}
//copy constructor function
template <class Elem>
BinaryHeap<Elem>::BinaryHeap(const BinaryHeap<Elem> &rhs) :MAX_SIZE(rhs.MAX_SIZE), CurrentNum(rhs.CurrentNum)
{
Data = new Elem[MAX_SIZE];
strcpy(Data, rhs.Data);
}
//array constructor
//将数组元素构建成最大堆
template <class Elem>
BinaryHeap<Elem>::BinaryHeap(Elem *Array, int ElemNum, int MaxSize) :MAX_SIZE(MaxSize)
{
Data = new Elem[MAX_SIZE];
CurrentNum = ElemNum;
for (int i = 0; i < ElemNum; ++i)
Data[i] = Array[i];
for (int i = ParentOf(CurrentNum - 1); i >= 0; --i) //单步向前,数组从后往前,树从上到下调整
HeapifyDown(i);
}
template <class Elem>
BinaryHeap<Elem>::~BinaryHeap(void)
{
if (Data)
delete[] Data;
}
3. 最大堆调整(最小堆只需修改比较即可)
template <class Elem>
int BinaryHeap<Elem>::ParentOf(int Node)
{
//assert(Node > 0);
return (Node - 1) / 2;
}
template <class Elem>
int BinaryHeap<Elem>::LeftChildOf(int Node)
{
return (Node * 2) + 1;
}
//最大堆调整:从Node位置向上调整(数组向前,Node后不管)
template <class Elem>
void BinaryHeap<Elem>::HeapifyUp(int Node)
{
int Current = Node;
int Parent = ParentOf(Node);
Elem Item = Data[Current];
while (Current > 0)
{
if (Item > Data[Parent])
{
Data[Current] = Data[Parent];
Current = Parent;
Parent = ParentOf(Current);
}
else
break;
}
Data[Current] = Item;
}
//最大堆调整:从Node位置向下调整(数组向后,Node前不管)
template <class Elem>
void BinaryHeap<Elem>::HeapifyDown(int Node)
{
int Current = Node;
int Child = LeftChildOf(Node);
Elem Item = Data[Current];
while (Child < CurrentNum)
{
if (Child < (CurrentNum - 1))
{
if (Data[Child] < Data[Child + 1])
++Child;
}
if (Item < Data[Child])
{
Data[Current] = Data[Child];
Current = Child;
Child = LeftChildOf(Current);
}
else
break;
}
Data[Current] = Item;
}
此处给出最大堆向上调整 HeapifyUp 演示:Node = ParentOf(CurrentNum - 1);
那什么时候向上调整,什么时候向下调整呢?很明显构建二叉堆的时候必须向上迭代(递归)调整,另外添加元素也必须向上调整;而移除堆顶元素(排序)后,则需要从上向下调整。也就是说当原数组不是二叉堆或是往后面添加元素打乱稳定时,需要从下往上调整;移除元素后,虽然数组不是二叉堆形式了,但是有一子树是不需移动的,只需调整另外一边子树。通俗点就是抽走前面的需要向下调整,后面添加需要向前调整。
4. 添加、移除元素
template <class Elem>
bool BinaryHeap<Elem>::Add(const Elem &Item)
{
if (CurrentNum >= MAX_SIZE)
return false;
Data[CurrentNum] = Item;
HeapifyUp(CurrentNum++);
return true;
}
template <class Elem>
Elem BinaryHeap<Elem>::Remove(void)
{
assert(CurrentNum > 0);
Elem Temp = Data[0];
Data[0] = Data[--CurrentNum]; //此处是用数组最后一个填补
HeapifyDown(0); //从上往下调整
return Temp;
//或选择键值大的儿子填补空位,重复该步骤直到最后一个元素填补了空位
}
5. 堆排序
template <class Elem>
inline int BinaryHeap<Elem>::GetSize(void)
{
return CurrentNum;
}
template <class Elem>
Elem* BinaryHeap<Elem>::Sort(void)
{
Elem *NewArray = new Elem[CurrentNum];
//升序
for (int ElemNum = CurrentNum - 1; ElemNum >= 0; --ElemNum)
NewArray[ElemNum] = Remove();
/*降序
int ElemNum = CurrentNum;
for (int i = 0; i < ElemNum; ++i)
NewArray[i] = Remove();
*/
return NewArray;
}