一、定义
1.优先级队列:
不同于队列这种数据结构,优先级队列元素出队列的顺序是由元素的优先级决定的,可以递增也可以是递减的。
2.大根树(小根树):
每个节点的值都大于(小于)或等于其子节点的值。
3.大根堆(小根堆):
既是大根树(小根数)也是完全二叉树。
二、大根堆的插入与删除操作
由于堆是完全二叉树,所以用数组描述最为高效。
1.插入:
插入操作比较简单,把新元素插入新节点,然后沿着新节点到根节点的路径,执行一次起泡操作。
实现代码:
//堆的插入方法,theElement表示优先级, 时间复杂度时O(logn)
template <class T>
void maxHeap<T>::push(const T & theElement) {
if (heapSize == arrayLength-1){
//数组容量不够了
throw 3;
}
int currentNode = ++heapSize;
while (currentNode != 1&&heap[currentNode/2]<theElement){
//current的父节点刚好是current/2向下取整
heap[currentNode] = heap[currentNode/2];
currentNode /= 2;
}
heap[currentNode] = theElement;
}
时间复杂度与高度有关是O(logn)。
2.删除操作
由于删除的是优先级最高的(最低的)所以删除根节点元素,将末节点元素取出,删除末节点,将根节点左右孩子中大的放到根节点,不断重复。
实现代码:
//删除节点的方法,时间复杂度是O(logn)
template <class T>
void maxHeap<T>::pop() {
if (heapSize == 0){
throw 4;
}
heap[1].~T();//删除堆顶
T lastElement = heap[heapSize--];//将堆的最后一个元素拿出来
int currentNode = 1,child = 2;
while (child <= heapSize)
{
//找出两个孩子中大的那一个
if (child<heapSize&&heap[child]<heap[child+1]){
child++;
}
if (lastElement>=heap[child]){
break;//末元素找到位置了
}
heap[currentNode] = heap[child];
currentNode = child;
child = child*2;//获得大孩子的左孩子
}
heap[currentNode] = lastElement;
}
时间复杂度与高度有关是O(logn)。
三、堆的初始化
1.可以利用插入操作,一个一个元素的插入。复杂度是O(nlogn)
2.直接利用数组元素初始化,复杂度是Θ(n)
原理:从第一个有孩子的节点开始一次重构为最大堆。
//堆的初始化,时间复杂度为Θ(n),比一个一个的插入性能要好一些
template <class T>
void maxHeap<T>::initialize(T *theHeap, int theSize) {
delete [] heap;
heap = theHeap;
heapSize = theSize;
//从第一个有孩子的节点开始排查
for (int root = heapSize/2; root >=1 ; --root) {
T theElement = heap[root];
int child = 2*root;
while (child<=heapSize){
if (child<heapSize&&heap[child]<heap[child+1]){
child++;//获取大孩子
}
if (theElement>=heap[child]){
break;
}
//父子交换
heap[child/2] = heap[child];
child *= 2;
}
heap[child/2] = theElement;
}
}
四、应用
1.堆排序
利用元素初始化最大堆,每次取出最大元素并删除,时间复杂度为O(nlogn)。
实现代码:
//堆排序在初始化和删除的基础上实现变得非常简单,事件复杂度为O(nlogn),初始化为O(n)排序为O(logn)
template <class T>
void maxHeap<T>::sort() {
T *temp = new T[heapSize+1];
int size = heapSize;//pop()方法导致heapsize大小在动态变化
for (int i = 1; i <= size ; ++i) {
T a = top();
pop();
temp[i] = a;
}
heap = temp;
//由于pop方法将size的大小减到了0所以必须将大小还原。
heapSize = size;
// cout<<heapSize<<"***********************"<<endl;
}
2.霍夫曼编码
实现代码:
template <class T>
linkedBinaryTree<int>* huffmanTree(T weight[],int n){
huffmanNode<T> *hNode = new huffmanNode<T>[n+1];
linkedBinaryTree<int> emptyTree;
//创建不同频率的霍夫曼节点
for (int i = 1; i <= n ; ++i) {
hNode[i].weight = weight[i];//频率是从数组的下标一开始计算的
hNode[i].tree = new linkedBinaryTree<int>;
hNode[i].tree->makeTree(i,emptyTree,emptyTree);
}
//使一组单节点树构成小根堆
minHeap<huffmanNode<T>> heap(1);
heap.initialize(hNode,n);
huffmanNode<T> w,x,y;
linkedBinaryTree<int> *z;
for (int j = 1; j < n ; ++j) {
//取出最小的两棵树
x = heap.top();heap.pop();
y = heap.top();heap.pop();
//将他们合成一棵树
z = new linkedBinaryTree<int>();
z->makeTree(0,*x.tree,*y.tree);
w.weight = x.weight+y.weight;
w.tree = z;
//将合成的树放到堆中
heap.push(w);
delete x.tree;
delete y.tree;
}
return heap.top().tree;
}