什么是二叉堆
对于优先队列的实现,最常用的数据结构就是二叉堆。二叉堆是一棵完全二叉树, 因此借用完全二叉树的规律我们可以自上而下,从左到右地对每一个节点进行标记。这时,二叉堆便可以用一个数组表示而不必使用树形结构。
① A
/ \ |0|1|2|3|4|5|6|7|8|
② B C ③ ---> |_|A|B|C|D|E|_|_|_|
/ \
④ D E ⑤
如上图所示,一棵完全二叉树可以用数组表示(数组从下标1开始)。
另外,根据完全二叉树的性质,可以得到对于非根节点,他们的父节点编号等于该节点编号除以2,即index(t) = index(t->left)/2 或 index(t) = index(t->right)/2
。
如果一个节点有两个孩子,那么左孩子的编号一定小于当前堆中节点的数量(currentSize),用index(t)*2 != currentSize
判断。如果左孩子的编号等于当前节点的数量(即最大的编号),那么该节点只有左孩子,没有右孩子。
二叉堆的堆序性质
对于最小堆中的任一节点X,X的子节点的值总大于或等于X的值。所以对于一个最小堆,得到堆中最小的元素只需要从堆顶取出即可,但是无法获得最大值。
同样对于最大堆,性质和最小堆相似,对于最大堆中的任一节点X,X的子节点的值总小于或等于X的值。
二叉堆的基本实现
以最小堆为例
#include<iostream>
#include<vector>
using namespace std;
template<typename T>
class binaryHeap{
public:
explicit binaryHeap(int capacity = 100){
array.resize(capacity + 1);
}
explicit binaryHeap(const vector<T> & items){
array.resize(items.size() + 10);
currentSize = items.size();
//插入数值
for (int i = 0; i < items.size(); i++){
array[i + 1] = items[i];
}
//整理成最小堆
buildHeap();
}
bool isEmpty(){
return currentSize == 0;
}
const T & findMin()const{
return array[1];
}
//插入一个新节点(上滤)
void insert(const T & x){
//如果数组空间满了
//就扩大一倍的空间
if (currentSize == array.size() - 1){
array.resize(array.size() * 2);
}
//在二叉堆的最后建立一个空穴
//hole代表空穴的位置
int hole = ++currentSize;
//当穴的父节点比x大的时候,且穴不在堆顶
while (hole > 1 && array[hole / 2] > x){
//父节点下移
array[hole] = array[hole / 2];
//穴上移
hole = hole / 2;
}
//当穴的父节点比x小,或穴移动到了堆顶
//就把x填进去
array[hole] = x;
}
//取出最小值
void deleteMin(){
if (isEmpty()){
return;
}
//把最后一个位置的数填入堆顶
array[1] = array[currentSize];
//删除最后一个位置
currentSize--;
//对堆顶进行下滤
percolateDown(1);
}
//取出最小值,赋值给x
void deleteMin(T & x){
if (isEmpty()){
return;
}
x = array[1];
array[1] = array[currentSize--];
//对堆顶下滤
percolateDown(1);
}
//打印整个堆
void printHeap(){
for (int i = 1; i <= currentSize; i++){
cout << array[i] << ' ';
}
cout << endl;
}
private:
int currentSize;
vector<T> array;
//对无序的堆下滤排序
void buildHeap(){
//从第一个非叶子节点开始下滤
//第一个非叶子节点的位置是size/2
//完全二叉树的性质
for (int i = currentSize / 2; i > 0; i--){
percolateDown(i);
}
}
/*
*下滤(堆重整)
*把以该节点为根的子树变成小根堆
*/
void percolateDown(int hole){
//穴的孩子位置
int child;
//获得穴的值
T temp = array[hole];
//当hole不是叶子节点的时候
while (hole * 2 <= currentSize){
child = hole * 2;
//如果有两个孩子,让child指向值最小的孩子
if (child != currentSize && array[child] > array[child + 1]){
child++;
}
//如果最小的孩子比穴小
//就将穴下移
if (temp > array[child]){
array[hole] = array[child];
hole = child;
}
//否则,即找到穴应在的位置
//退出循环
else{
break;
}
}
//将值填入穴
array[hole] = temp;
}
};
int main(){
vector<int>a = { 150, 80, 40, 30, 10, 70, 110, 100, 20, 90, 60, 50, 120, 140, 130 };
binaryHeap<int>bh(a);
for (int i = 0; i < a.size(); i++){
cout << a[i] << ' ';
}
cout << endl;
bh.printHeap();
bh.deleteMin();
bh.printHeap();
system("pause");
return 0;
}