堆是一种完全二叉树。
- 最大堆:对树中的每个结点来说,其结点的值都大于等于其孩子结点的值。
- 最小堆:对树中的每个结点来说,其结点的值都小于等于其孩子结点的值。
- 特性:最大堆中的最大值在堆顶,最小堆中的最小值在堆顶。
堆中操作:
- 搜索堆顶元素。O(1)
- 添加新结点。先将新结点添加到最后的位置,然后将该新结点的值与其父节点的值相比较,在最大堆中如果新结点值比父结点值大(也即父节点值比新结点值小,不符合最大堆的定义了,需要调整),则交换父子结点的数据;按照此方式逐层向上调整,直到到达堆顶或者父节点值大于等于新节点值。O(log(n))
- 删除堆顶元素。在以vector为底层容器实现的最大堆中,堆顶元素并不直接删除,而是先将其(索引为0)与最后一个元素(索引为n-1)交换,然后堆的大小减一,vector中有效元素少一个,相当于删除了堆顶元素;然后将堆顶元素的值与其左右子结点元素的值相比,如果其左(右)孩子结点值在三者中最大,则交换根和左(右)孩子结点,然后递归调整左(右)孩子结点,直到到达叶子结点或者根节点值比左右孩子结点值都大。O(log(n))
最大堆(最小堆)常用优先队列(默认底层容器类是vector)实现。
最小堆实现
#include <iostream>#include <vector>using namespace std;/*** 以vector为底层容器的最小堆(完全二叉树)* 有效元素从索引为0的位置开始存储,* 索引为i的元素的左、右结点索引分别是2*i+1, 2*i+2,* 索引为i的元素的父节点的索引是(i-1)/2,* 最后一个非叶子节点索引为n/2-1。*/template <class DT>class MinHeap{private:vector<DT> data; // 存储元素使用的线性表int n; // data中有效元素个数void heapAdjust(int k){// 将data中索引位置在[0,k]的元素调整为最小堆// 有效元素k+1个,第一个非叶子节点的位置是(k+1)/2-1int node_idx = (k+1)/2-1;while(node_idx >= 0){adjustNode(node_idx);//this->print();node_idx --;}}void adjustNode(int i){// 调整索引i位置的结点,它一定有左孩子,不一定有右孩子int lc_idx = 2 * i + 1; // 索引i的左子结点索引int rc_idx = lc_idx + 1; // 索引i的右子结点索引if(lc_idx >= n){// 左孩子不存在,那右孩子一定不存在,无需调整了。return;}else if(rc_idx >= n){// 左孩子存在,右孩子不存在if(data[i] < data[lc_idx]){// 根节点值小于左子结点,无需调整return;}else{// 根节点值大于左子结点,交换根和左子结点this->swap(i, lc_idx);// 由于右孩子结点不存在,因此左孩子结点一定是叶子结点,无需递归调整左子节点}}else{// 左、右孩子都存在if(data[i] < data[lc_idx] && data[i] < data[rc_idx]){// 根结点是最小的,无需调整return;}else if(data[lc_idx] < data[i] && data[lc_idx] < data[rc_idx]){// 左子结点最小的,根与左子结点交换,然后递归调整左子结点this->swap(i, lc_idx);adjustNode(lc_idx);}else{// 右子结点是最小的,根与右子结点交换,然后递归调整右子结点this->swap(i, rc_idx);adjustNode(rc_idx);}}}void swap(int i, int j){// 交换索引位置在i,j处的元素DT tmp = this->data[i];this->data[i] = this->data[j];this->data[j] = tmp;}void heapUp(int i){// 新插入时调用,将索引为i的元素的值调整到正确的位置上// 要求原来数据已经是最小堆int root_idx = (i-1)/2;if(root_idx < 0 || data[root_idx] < data[i]){return;}else{// 新结点值比根结点小this->swap(root_idx, i);this->heapUp(root_idx);}}void heapDown(int i){// 删除结点后调用int lc_idx = 2 * i + 1; // i的左孩子索引int rc_idx = lc_idx + 1; // i的右孩子索引if(lc_idx >= this->n){// 左孩子不存在,则右孩子肯定也不存在,无需调整return;}else if(rc_idx >= this->n){// 左孩子存在,右孩子不存在if(data[i] < data[lc_idx]){// 根节点值小于左孩子结点值,已经是最小堆,无需调整return;}else{// 根节点值大于左孩子结点值,需要交换根和左孩子this->swap(i, lc_idx);// 因为左孩子已经是叶子结点,没有子树了,不用递归调整}}else{// 左右子结点都存在if(data[i] < data[lc_idx] && data[i] < data[rc_idx]){// 根结点是三个中最小的,符合最小堆,无需调整了return;}else if(data[lc_idx] < data[i] && data[lc_idx] < data[rc_idx]){// 左孩子是三者中最小的,交换根和左孩子结点this->swap(i, lc_idx);// 递归向下调整左孩子结点this->heapDown(lc_idx);}else{// 右孩子是三者中最小的,交换根和右孩子结点this->swap(i, rc_idx);// 递归向下调整右孩子结点this->heapDown(rc_idx);}}}public:MinHeap():n(0){}MinHeap(vector<DT> _data): data(_data), n(data.size()){this->heapAdjust(n-1);}void push(DT x){if(this->data.size() < n){// data中有空余位置this->data[n] = x;}else{// data中每个位置都有元素,也即所有元素都是有效元素this->data.push_back(x);}n += 1;this->heapUp(n-1);this->print();}void pop(){if(this->empty()){return;}this->swap(0, n-1);n -= 1;this->heapDown(0);this->print();}DT& top(){return data[0];}bool empty(){return this->n <= 0;}void print() {cout << "[";if (n > 0) {cout << this->data[0];}for (int i = 1; i < n; i++) {cout << "," << this->data[i];}cout << "]" << endl;}};int main() {MinHeap<int> mh(vector<int>({7, 4, 3, 5, 9, 8, 1, 2}));vector<int> nv({91, 14, 23, 41, 22, 11, 87, 90});for(int v : nv){mh.push(v);}while (!mh.empty()){cout << mh.top() << " , ";mh.pop();}cout << endl;return 0;}
