二叉堆可以用来实现优先队列。二叉堆是一棵完全二叉树,除了叶子节点那一层,其他的层都是满的,叶子节点那一层是从左往右依次存放的,因此它们的父子元素的索引有数学公式可以参考,对于节点i来说,它的左儿子是2i,右儿子是2i+1,它的父亲节点是i/2向下取整,因此,在其内部我们可以通过数组来实现元素的存储,从而不需要链式结构,可以节约存储空间,提高访问速度(但是如果有合并操作,则会很慢,此时使用链式结构是不错的选择,可以采用左式堆或者斜堆以及二项队列)。对于二叉堆来说,它具有堆序特性,及父节点小于(小顶堆)或者大于(大顶堆(STL中的优先队列默认是大顶堆))儿子节点。
下面记录一下今天的代码:
首先是二叉堆的实现模板
#ifndef MY_BINARY_HEAP_H
#define MY_BINARY_HEAP_H
#include<vector>
#include<iostream>
using namespace std;
/*
* 实现小顶堆
*/
template<typename Comparable>
class MyBinaryHeap
{
public:
explicit MyBinaryHeap(int Capacity = 100)
: array(Capacity + 1), currentSize(0){}
explicit MyBinaryHeap(const vector<Comparable> & items)
: array(items.size() + 10), currentSize(items.size())
{
for (unsigned int i = 0; i < items.size(); i++)
{
array[i + 1] = items[i];
}
buildHeap();
}
explicit MyBinaryHeap(const MyBinaryHeap<Comparable> & rhs)
: array(rhs.array), currentSize(rhs.currentSize){}
bool isEmpty(){ return 0 == currentSize; }
const Comparable & findMin() const{ return array[1]; }
int getSize() { return currentSize; }
void insert(const Comparable & x);
void deleteMin();
void deleteMin(Comparable & minItem);
void makeEmpty(){
currentSize = 0;
}
private:
int currentSize; //存放当前heap中的元素
vector<Comparable> array; //数组第一个元素,即index == 0 的位置上没有元素
//从index==1的位置开始放置元素,这样方便对数组下标和元素位置进行统一操作
void buildHeap();
void percolateDown(int hole);
};
/*
* 建立堆序
* 时间为O(N)
*/
template<typename Comparable>
void MyBinaryHeap<Comparable>::buildHeap()
{
for (int i = currentSize / 2; i > 0; i--)
percolateDown(i);
}
/*
* 在二叉堆中插入一个元素
* 首先在放在最后的位置上,然后不断通过上滤(percolate up)算法
* 一直找到比它小的节点,此时插入的元素成为这个节点的子树
* 如果到达根部 hole==1,且根比它小,则它就成为根
* 插入的时间为 O(logN)
*/
template<typename Comparable>
void MyBinaryHeap<Comparable>::insert(const Comparable & x)
{
if (currentSize == array.size() - 1)
array.resize(array.size() * 2);
int hole = ++currentSize;
for (; hole > 1 && x < array[hole / 2]; hole /= 2)
{
array[hole] = array[hole / 2];
}
array[hole] = x;
}
/*
* 删除最小项
* 如果本来二叉堆就是空的,执行删除最小的元素这个操作时,在标准错误输出上打印,并停机
*/
template<typename Comparable>
void MyBinaryHeap<Comparable>::deleteMin()
{
if (isEmpty())
{
cerr << "此时堆内为空,不能删除最小元素" << endl;
exit(1);
}
array[1] = array[currentSize--];
percolateDown(1);
}
/*
* 删除最小项,并且把最小值放在minItem中
* 如果本来二叉堆就是空的,执行删除最小的元素这个操作时,在标准错误输出上打印,并停机
*/
template<typename Comparable>
void MyBinaryHeap<Comparable>::deleteMin(Comparable & minItem)
{
if (isEmpty())
{
cerr << "此时堆内为空,不能删除最小元素" << endl;
exit(1);
}
minItem = array[1];
array[1] = array[currentSize--];
percolateDown(1);
}
/*
* 这个私有的方法主要用来实现下滤的算法(percolate down)
* 参数hole指示了下滤开始的节点
* 平均时间O(logN)
*/
template<typename Comparable>
void MyBinaryHeap<Comparable>::percolateDown(int hole)
{
int child;
Comparable tmp = array[hole];
for (; hole * 2 <= currentSize; hole = child)
{
child = hole * 2; // 尝试对左儿子进行比较
if (child != currentSize && array[child + 1] < array[child])
child++; // 如果右儿子比左儿子小,则尝试对右儿子进行比较
if (array[child] < tmp) // 如果下滤下来的这个点比两个儿子中最小的大,最小的成为此时子树的根
array[hole] = array[child];
else
break;
}
array[hole] = tmp;
}
#endif
然后是测试代码:
#include"MyBinaryHeap.h"
#include<vector>
#include<random>
#include<ctime>
#include<iostream>
using namespace std;
int main()
{
vector<int> tmpVec;
MyBinaryHeap<int> BH1;//使用无参的构造函数
default_random_engine e(static_cast<unsigned int>(time(0)));
uniform_int_distribution<int> u(0,100);
cout << "测试插入数据:" << endl;
cout << "==== 原始数据为:";
for (int i = 0; i < 15; ++i)
{
int tmp = u(e);
cout << tmp << ' ';
BH1.insert(tmp);
tmpVec.push_back(tmp);
}
MyBinaryHeap<int> BH2(tmpVec);//通过数组来构造
cout << "此时有:" << BH1.getSize() <<"个元素,其中最小值为:" << BH1.findMin() << endl;
cout << "测试拷贝构造函数:" << endl;
MyBinaryHeap<int> BH3(BH1);//拷贝构造函数
cout << "==== 通过取最小值,获得从小到大的排序 =====" << endl;
cout << "BH1:" << endl;
while (!BH1.isEmpty())
{
int tmp = 0;
BH1.deleteMin(tmp);
cout << tmp << " ";
}
cout << endl;
cout << "BH2:" << endl;
while (!BH2.isEmpty())
{
int tmp = 0;
BH2.deleteMin(tmp);
cout << tmp << " ";
}
cout << endl;
cout << "BH3:" << endl;
while (!BH3.isEmpty())
{
int tmp = 0;
BH3.deleteMin(tmp);
cout << tmp << " ";
}
cout << endl;
return 0;
}
测试的结果为: