何为二叉堆?
二叉堆是一种特殊的堆,二叉堆是完全二叉树或者近似完全二叉树。二叉堆满足堆特性:父节点的键值总是保持固定的序关系于任何一个子节点的键值,且每个节点的左右子树都是一个二叉堆。
当父节点的键值总是大于或等于任何一个子节点的键值时为最大堆(大顶堆)。
当父节点的键值总是小于或等于任何一个子节点的键值时为最小堆(小顶堆)。
根据二叉树的性质,可得:
1. 如果根节点在数组的位置是1,第n个位置的子节点分别在2n和2n+1,第n个位置的双亲节点在[i/2]。因此,第1个位置的子节点在2和3。
2. 如果根节点在数组的位置是0,第n个位置的子节点分别在2n+1和2n+2,第n个位置的双亲节点在[(i-1)/2]。因此,第0个位置的子节点在1和2。
二叉堆一般用数组来表示,得益于数组的随机存储能力,我们能够很快确定堆中节点的父节点和子节点。
在本文中,我们主要介绍大顶堆的C++模板实现过程,以根节点在0位置存储为例。
二叉堆的具体实现
1. 二叉堆的抽象数据类型
/*大顶堆类定义*/
template <typename T>
class MaxHeap
{
public:
MaxHeap(int cap);
~MaxHeap();
bool insert(T val); //往二叉堆中插入元素
bool remove(T val); //从二叉堆中删除元素
void print(); //打印二叉堆
T getTop(); //获取堆顶元素
bool creatMaxHeap(T a[],int size);//根据指定数组创建大顶堆
private:
int ArraySize; //数组的大小
int size; //堆的大小,数组中有效元素的个数
T* maxheap; //底层数组
//从index节点往根节点调整堆
void filterUp(int index);
//从begin节点向end节点调整堆
void filterDown(int begin,int end);
};
2. 构造函数与析构函数
/*构造函数*/
template <typename T>
MaxHeap<T>::MaxHeap(int cap)
{
ArraySize = cap; //初始化数组的大小
size = 0;
maxheap = new T[ArraySize]; //开辟空间
};
/*析构函数*/
template <typename T>
MaxHeap<T>::~MaxHeap()
{
delete []maxheap; //释放空间
}
3. 大顶堆的插入
在数组的最末尾插入节点,然后自上而下地调整子节点与父节点的位置:比较当前节点与父节点的大小,若不满足大顶堆的性质,则交换两节点。时间复杂度为O(log n)。
/*从下往上调整堆--插入元素时使用*/
template <typename T>
void MaxHeap<T>::filterUp(int index)
{
//记录下当前节点
T value = maxheap[index];
while(index>0)
{
//得到其双亲节点
int indexparent = (index - 1) / 2;
if(value < maxheap[indexparent])
break;
else //交换两节点
{
//新节点被其父节点覆盖
maxheap[index] = maxheap[indexparent];
//记录下新节点的位置
index = indexparent;
}
}
//给新节点赋值
maxheap[index] = value;
};
/*插入元素*/
template<typename T>
bool MaxHeap<T>::insert(T val)
{
if(ArraySize == size) //如果数组放不下
return false;
maxheap[size] = val; //数组末尾放入新节点
filterUp(size); //自下而上调整节点
size++; //堆的元素数量加1
return true;
};
实际编码过程中,我们不进行节点的交换,我们直接使用父节点覆盖当前节点,然后记录新节点的位置,最后直接把新节点放入它最后的位置即可。
4. 大顶堆的删除
堆删除节点的过程:用数组最末尾节点覆盖被删结点,然后数组的有效元素(即堆中的元素)数量减1,再由该节点从上到下调整二叉堆。
/*从上到下调整堆--删除元素时使用*/
template <typename T>
void MaxHeap<T>::filterDown(int current,int end)
{
int child = current * 2 + 1; //当前结点的左孩子
T value = maxheap[current]; //保存当前节点
while(child <= end)
{
//选出两个孩子中较大的孩子
if(child < end && maxheap[child] < maxheap[child + 1])
child++;
if(value > maxheap[child])//无需调整
break;
else
{
maxheap[current] = maxheap[child]; //孩子节点覆盖当前节点
current = child; //当前节点为较大的孩子节点
child = child * 2 + 1; //child重新指向当前节点的左孩子节点
}
}
maxheap[current] = value; //找到合理地位置后 赋值
};
/*删除元素*/
template <typename T>
bool MaxHeap<T>::remove(T key)
{
if(size == 0)
return false;
int index; //被删除元素的下标
for(index = 0; index < size; index++)
{
if(maxheap[index] == key)
break; //找到被删元素的下标
}
if(index == size)
return false; //数组中没有要删除的元素
//使用数组最后一个元素来代替该被删除元素
maxheap[index] = maxheap[size - 1];
//覆盖完成后 从上到下调整堆
filterDown(index,size--);
return true;
};
5. 其他操作
/*获取堆顶元素*/
template <typename T>
T MaxHeap<T>::getTop()
{
if(size != 0)
return maxheap[0];
};
/*打印大顶堆*/
template <typename T>
void MaxHeap<T>::print()
{
for(int i=0; i<size; i++)
cout << maxheap[i] << " ";
cout << endl;
};
/*根据指定数组创建大顶堆*/
template <typename T>
bool MaxHeap<T>::creatMaxHeap(T a[],int size)
{
if(ArraySize < size)
return false;
for(int i=0; i<size; i++)
insert(a[i]);
return true;
};
6. 测试代码
int main()
{
MaxHeap<int> heap1(11);
//逐个元素创建大顶堆
for(int i=0; i<10; i++)
heap1.insert(i);
cout << "堆顶元素为:" << heap1.getTop() << endl;
heap1.print();
/*从堆中删除元素5*/
heap1.remove(5);
heap1.print();
//根据指定数组创建大顶堆*/
int array[10] = { 1,2,3,4,5,6,7,8,9,10 };
MaxHeap<int> heap2(11);
heap2.creatMaxHeap(array,10);
cout << "堆顶元素为:" << heap2.getTop() << endl;
heap2.print();
return 0;
}
7. 输出结果
堆顶元素为:9
9 8 5 6 7 1 4 0 3 2
9 8 4 6 7 1 2 0 3
堆顶元素为:10
10 9 6 7 8 2 5 1 4 3
ok,all right.