什么是堆
堆是一种重要的数据类型,是一种特殊的完全二叉树。
堆的递归定义为:
1)堆可以是一棵空树。
2)如果堆不为空,则堆的根结点比所有子孙结点大(或小)。
3)堆的子树也是堆。
根据堆的定义,可以得出:从堆的根结点沿任意路径到叶节点,经过的结点具有有序性。
例:如下图所示的一棵二叉树
从树的根结点到叶节点的全体路径为:
10 7 5
10 7 1
10 2
全体路径都是从大到小的序列,并且该二叉树是完全二叉树,故该二叉树是堆。
像这样沿任意路径,结点递降的堆,叫做最大堆(或大根堆);同理,沿任意路径,结点递增的堆,叫做最小堆(或小根堆)。
堆的基本操作:最大堆为例
堆是一棵完全二叉树,所以可以用数组实现堆。
template<class T, int MaxSize> //T是堆中存放的数据类型,MaxSize是堆的最大容量
class MaxHeap {
private:
T buffer[MaxSize]; //存放堆的数组
int _size; //堆的当前数据量
static T out_of_range; //试图访问空堆时,返回越界数据
public:
MaxHeap(); //构造函数
void insert(T const&insert_element); //插入操作
T pop_max(); //弹出最大数据:根结点对应的数据
void clear(); //清空堆
bool empty()const; //判断堆是否为空
bool full()const; //判断堆是否为满
int size()const; //返回堆的当前数据线
int max_size()const; //返回堆的最大容量
};
最大堆的插入:向上调整
最大堆的插入分两步进行:
1)插入:和完全二叉树一样,直接把数据插入到堆的最后一个结点末尾。
2)有序性调整:前文提到,从堆的根结点沿任意路径到叶节点,经过的结点具有有序性。但是插入的新结点可能破坏堆的有序性,所以需要从下向上调整。
具体的调整方法为:从插入的结点开始,依次和父结点比较,如果父结点较小,就交换父结点与插入结点,直到插入结点不大于父结点为止。
template<class T, int MaxSize>
void MaxHeap<T, MaxSize>::insert(T const&insert_element) {
if (full())return; //如果堆满,则不进行插入
buffer[_size] = insert_element; //在堆的最后一个结点末尾插入数据
if (!_size++)return; //如果堆为空,则可以直接返回
int pos = _size - 1, father; //pos记录插入结点下标,father记录父结点下标
while (pos) { //当插入结点调整为根结点时结束循环
father = (pos - 1) / 2; //由完全二叉树的性质,父结点的下标由左式给出
if (buffer[father] < buffer[pos]) { //如果父结点较小,就交换父结点与插入结点
auto temp = buffer[father];
buffer[father] = buffer[pos];
buffer[pos] = temp;
pos = father;
}
else break; //插入结点不大于父结点,有序性调整结束
}
}
弹出最大数据:向下调整
由最大堆的定义,最大数据在根结点上,所以需要删除根结点,并返回根结点的数据。
最大堆的删除分三步进行:
1)交换:交换根结点与最后一个结点的位置,方便删除操作。
2)删除:由于根结点与最后一个结点换位,只需要删除最后一个结点。
2)有序性调整:最后一个结点被换到根结点上,很可能破坏堆的有序性,所以需要从上向下调整。
具体的调整方法为:从跟结点开始,依次和较大的子结点比较,如果父结点较小,就交换父结点与较大子结点,直到根结点不小于任意子结点为止。
template<class T, int MaxSize>
T MaxHeap<T, MaxSize>::pop_max() {
if (empty())return out_of_range; //如果堆为空,则直接返回
auto output = *buffer; //因为要删除根结点,先记录下根结点的数据
*buffer = buffer[_size-- - 1]; //交换根结点与最后一个结点,并删除最后一个结点
if (1 == _size)return output; //如果堆只剩下一个结点,则直接返回原根结点数据
int pos = 0, l_child, r_child; //pos记录根结点下标,l_child和r_child分别记录左右子结点下标
while (2 * pos + 1 < _size) { //当根结点被调整到叶节点上时,结束有序性调整
l_child = 2 * pos + 1;
r_child = 2 * pos + 2; //由完全二叉树的性质,子结点的下标由左式给出
if (2 * pos + 2 == _size)
if (buffer[l_child] > buffer[pos]) {
auto temp = buffer[l_child];
buffer[l_child] = buffer[pos];
buffer[pos] = temp;
pos = l_child;
}
else break;
else if (buffer[l_child] > buffer[r_child]) //选择较大的子结点,与根结点比较
if (buffer[l_child] > buffer[pos]) { //如果根结点较小,则交换根结点与该子结点
auto temp = buffer[l_child];
buffer[l_child] = buffer[pos];
buffer[pos] = temp;
pos = l_child;
}
else break; //根结点不小于任意子结点,有序性调整结束
else if (buffer[r_child] > buffer[pos]) {
auto temp = buffer[r_child];
buffer[r_child] = buffer[pos];
buffer[pos] = temp;
pos = r_child;
}
else break;
}
return output; //返回原根结点的数据
}
最小堆的操作与最大堆类似,本文不做赘述。
使用堆的注意事项
如果堆储存的是用户定义的数据类型,则需要重载关系运算符,或者指定一个键值作为比较数据大小的基准。
例:用最小堆实现哈夫曼编码。
构造哈夫曼树的依据是结点的权值,所以应该指定结点的权值为键值。
完整代码
//堆:数组实现
#ifndef OCHEAP_H
#define OCHEAP_H
#ifndef _OC_BEGIN
#define _OC_BEGIN namespace OC{
#endif
#ifndef _OC_END
#define _OC_END }
#endif
#ifndef OCHEAP_DEBUG
#define OCHEAP_DEBUG 0
#include<iostream>
using std::cout;
using std::endl;
#endif
_OC_BEGIN
//_MAXHEAP_BEGIN
template<class T, int MaxSize>
class MaxHeap {
private:
T buffer[MaxSize];
int _size;
static T out_of_range;
public:
MaxHeap();
void insert(T const&insert_element);
T pop_max();
void clear();
bool empty()const;
bool full()const;
int size()const;
int max_size()const;
#if OCHEAP_DEBUG
void LevleOrderTraversal()const {
auto it = buffer;
for (int count = 0; count != _size; count++)
cout << *it++ << ' ';
}
#endif
};
template<class T, int MaxSize>
T MaxHeap<T, MaxSize>::out_of_range;
template<class T, int MaxSize>
MaxHeap<T, MaxSize>::MaxHeap() :_size(0) {
}
template<class T, int MaxSize>
void MaxHeap<T, MaxSize>::insert(T const&insert_element) {
if (full())return;
buffer[_size] = insert_element;
if (!_size++)return;
int pos = _size - 1, father;
while (pos) {
father = (pos - 1) / 2;
if (buffer[father] < buffer[pos]) {
auto temp = buffer[father];
buffer[father] = buffer[pos];
buffer[pos] = temp;
pos = father;
}
else break;
}
}
template<class T, int MaxSize>
T MaxHeap<T, MaxSize>::pop_max() {
if (empty())return out_of_range;
auto output = *buffer;
*buffer = buffer[_size-- - 1];
if (1 == _size)return output;
int pos = 0, l_child, r_child;
while (2 * pos + 2 < _size) {
l_child = 2 * pos + 1;
r_child = 2 * pos + 2;
if (buffer[l_child] > buffer[r_child])
if (buffer[l_child] > buffer[pos]) {
auto temp = buffer[l_child];
buffer[l_child] = buffer[pos];
buffer[pos] = temp;
pos = l_child;
}
else break;
else if (buffer[r_child] > buffer[pos]) {
auto temp = buffer[r_child];
buffer[r_child] = buffer[pos];
buffer[pos] = temp;
pos = r_child;
}
else break;
}
return output;
}
template<class T, int MaxSize>
void MaxHeap<T, MaxSize>::clear() {
_size = 0;
}
template<class T, int MaxSize>
bool MaxHeap<T, MaxSize>::empty()const {
return _size ? false : true;
}
template<class T, int MaxSize>
bool MaxHeap<T, MaxSize>::full()const {
return _size == MaxSize ? true : false;
}
template<class T, int MaxSize>
int MaxHeap<T, MaxSize>::size()const {
return _size;
}
template<class T, int MaxSize>
int MaxHeap<T, MaxSize>::max_size()const {
return MaxSize;
}
//_MAXHEAP_END
//_MINHEAP_BEGIN
template<class T, int MaxSize>
class MinHeap {
private:
T buffer[MaxSize];
int _size;
static T out_of_range;
public:
MinHeap();
void insert(T const&insert_element);
T pop_min();
void clear();
bool empty()const;
bool full()const;
int size()const;
int max_size()const;
#if OCHEAP_DEBUG
void LevleOrderTraversal()const {
auto it = buffer;
for (int count = 0; count != _size; count++)
cout << *it++ << ' ';
}
#endif
};
template<class T, int MaxSize>
T MinHeap<T, MaxSize>::out_of_range;
template<class T, int MaxSize>
MinHeap<T, MaxSize>::MinHeap() :_size(0) {
}
template<class T, int MaxSize>
void MinHeap<T, MaxSize>::insert(T const&insert_element) {
if (full())return;
buffer[_size] = insert_element;
if (!_size++)return;
int pos = _size - 1, father;
while (pos) {
father = (pos - 1) / 2;
if (buffer[father] > buffer[pos]) {
auto temp = buffer[father];
buffer[father] = buffer[pos];
buffer[pos] = temp;
pos = father;
}
else break;
}
}
template<class T, int MaxSize>
T MinHeap<T, MaxSize>::pop_min() {
if (empty())return out_of_range;
auto output = *buffer;
*buffer = buffer[_size-- - 1];
if (1 == _size)return output;
int pos = 0, l_child, r_child;
while (2 * pos + 2 < _size) {
l_child = 2 * pos + 1;
r_child = 2 * pos + 2;
if (buffer[l_child] < buffer[r_child])
if (buffer[l_child] < buffer[pos]) {
auto temp = buffer[l_child];
buffer[l_child] = buffer[pos];
buffer[pos] = temp;
pos = l_child;
}
else break;
else if (buffer[r_child] < buffer[pos]) {
auto temp = buffer[r_child];
buffer[r_child] = buffer[pos];
buffer[pos] = temp;
pos = r_child;
}
else break;
}
return output;
}
template<class T, int MaxSize>
void MinHeap<T, MaxSize>::clear() {
_size = 0;
}
template<class T, int MaxSize>
bool MinHeap<T, MaxSize>::empty()const {
return _size ? false : true;
}
template<class T, int MaxSize>
bool MinHeap<T, MaxSize>::full()const {
return _size == MaxSize ? true : false;
}
template<class T, int MaxSize>
int MinHeap<T, MaxSize>::size()const {
return _size;
}
template<class T, int MaxSize>
int MinHeap<T, MaxSize>::max_size()const {
return MaxSize;
}
//_MINHEAP_END
_OC_END
#endif