二叉堆(也叫堆)是一个部分排序的二叉树,其排序规则体现在它的堆序性质上:最大堆和最小堆,最大堆就是其对于任一节点,每个节点的键值都大于等于它的孩子节点,所以根节点键值最大。最小堆则相反。
堆是一棵完全二叉树,具备完全二叉树的性质,可以用一个数组表示而不需要指针,在起始位置为 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 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; }