二叉堆详解实现优先级队列
二叉堆主要操作有2个:sink(下沉)和 swim(上浮),用以维护二叉堆的性质。
其主要应用有两个,首先是一种排序方法【堆排序】,第二是一种很有用的数据结构【优先级队列】。
二叉堆
首先,二叉堆和二叉树有啥关系呢,为什么人们总数把二叉堆画成一棵二叉树?
因为,二叉堆其实就是一种特殊的二叉树(完全二叉树),只不过存储在数组里。一般的链表二叉树,我们操作节点的指针,而在数组里,我们把数组索引作为指针:
// 父节点的索引
int parent(int root) {
return root / 2;
}
// 左孩子的索引
int left(int root) {
return root * 2;
}
// 右孩子的索引
int right(int root) {
return root * 2 + 1;
}
把 arr[1] 作为整棵树的根的话,每个节点的父节点和左右孩子的索引都可以通过简单的运算得到,这就是二叉堆设计的一个巧妙之处。
二叉堆还分为最大堆和最小堆。最大堆的性质是:每个节点都大于等于它的两个子节点。类似的,最小堆的性质是:每个节点都小于等于它的子节点。
两种堆核心思路都是一样的,本文以最大堆为例讲解。
2. 优先级队列
- 优先级队列这种数据结构有一个很有用的功能,你插入或者删除元素的时候,元素会自动排序,这底层的原理就是二叉堆的操作。
- 数据结构的功能无非增删查该,优先级队列有两个主要 API,分别是 insert 插入一个元素和 delMax 删除最大元素(如果底层用最小堆,那么就是 delMin)。而这两种API都可以通过上沉和下浮实现。
3. 代码实现
#include <iostream>
using namespace std;
class PriorityQueue
{
pritvate:
int* pArray;
int m_length;
public:
PriorityQueue(int N){
// 根节点直接从pArray[1[开始,所以长度要N+1
pArray = new int[N + 1];
m_length = 0;
}
//上浮
void swim(int k){ //k为数组的id
//若叶子节点k所对应的值大于其父节点,上浮
while(k > 1 && pArray[k] > pArray[k/2]){
swap(pArray[k], pArray[k/2]); //交换父子位置
k /= 2; //数值对应id改变
}
}
//下沉
void sink(int k){
while(k *2 <= m_length){ //循环至最后一个节点截止
int j = 2*k;
if (j < m_length && (pArray[j] < pArray[j + 1])) j++; // 这里先比较左子树和右子树的大小,将最大的那个键记录下来再和父节点比较
if (pArray[k] > pArray[j]) break; // 和父节点比较如果父节点比最大的子节点还要大,则直接退出循环
swap(pArray[k], pArray[i]); // 如果父节点比子节点小则交换
k = j;
}
}
//插入insert
void insert(int v){
pArray[m_length++] = v;
sink(m_length);
}
//删除delMax
int delMax(){
int max = pArray[1];
swap(pArray[1], pArray[m_length--]);
pArray[m_length+1] = NULL;
swim(1);
return max;
}
// 判断是否为空
bool isEmpty() {
return m_length == 0;
}
int size() {
return m_length;
}
}