堆这种数据结构在面试中还是经常容易被问到的,除了基本的堆排序,还有就是经典的TopK问题都可以用堆来实现。堆其实是一种完全二叉树。它可以方便的用数组来存储,而不是采用二叉链表存储。所以说堆底层维护的还是一个数组,只不过对其进行了封装。下面我们就来实现一个大顶堆吧。
#include <iostream>
#include <algorithm>
using namespace std;
template <typename Item>
class MaxHeap{
private:
Item *data;
int count;
int capacity;
// 对 数组中索引为k 位置的元素进行shiftUp操作
void shiftUp(int k){
while(k > 1 && data[k/2] < data[k]){
swap(data[k/2], data[k]);
k /= 2;
}
}
void shiftDown(int k){
while(2*k <= count){
int j = 2*k; // j首先默认为左孩子
// 下移操作时,如果既有左孩子,又有右孩子,应该与左右孩子中较大的交换
if(j+1 <= count && data[j+1] > data[j]){
j++;
}
if(data[k] >= data[j]){
break;
}
swap(data[k], data[j]);
k = j;
}
}
public:
// 构造函数
MaxHeap(int capacity){
// 堆的底层其实是维护一个数组
// 数组的0索引位置不放元素,从1索引位置放
// 这样更清晰一些
data = new Item[capacity + 1];
count = 0;
this -> capacity = capacity;
}
// 析构函数
~MaxHeap(){
delete[] data;
}
int size(){
return count;
}
bool isEmpty(){
return count == 0;
}
// 向堆中插入元素,插入元素后,堆仍然是一个堆
void insert(Item item){
// 断言 插入元素后不能超过数组的容量
assert(count + 1 <= capacity);
data[count + 1] = item;
count++;
shiftUp(count);
}
Item extractMax(){
assert(count > 0);
// 弹出 堆顶元素,
Item ret = data[1];
// 继续保持堆结构
swap(data[1], data[count]);
count--;
shiftDown(1);
return ret;
}
Item getMax(){
assert( count > 0 );
return data[1];
}
};
堆这种数据结构,对入堆操作和弹出最大值操作的时间复杂度都是O(logN),通过这种平衡,使其得到很好的平衡。追重要的两个操作就是 shiftUp和shiftDown操作,其他的书籍中可能介绍的叫做heapAdjust操作。