简介
最大堆是一种很常见的数据结构;下面我们先了解一下什么是堆;
堆是具有下列性质的完全二叉树:每个节点的值都大于或等于其左右孩子节点的值,称最大堆;或者每个节点的值都小于或者等于其左右孩子节点的值,称最小堆;下图左边是最大堆,右边是最小堆;我们这里以最大堆进行说明,最小堆和最大堆类似。
程序
1. 前期准备
堆是一种完全二叉树,而完全二叉树常见的表达方式是树的结构或者数组。这里我们采用数组的形式来描述最大堆;
class heap {
public:
heap() : n(0) , nums(vector<int>()){ }
heap(initializer_list<int> datas) {
for (auto data : datas)
push(data);
}
void push(int data);
int top();
void pop();
int size() {
return n;
}
private:
void adjust(int index);
vector<int> nums;
int n;
};
nums保存堆的数据,n记录堆中元素的个数; 最大堆常见的操作有3个:插入(push),取出最大值(top)以及删除最大值(pop)。
2. 插入函数【从下往上】
思路:将新的值放到数组最后面,然后与它的头节点进行比较;如果头节点的值比它小,就交换数据,并继续和新的头节点比较;如果头节点的值比它大,则说明现在已经满足最大堆的条件。
void heap::push(int data) {
++n;
nums.push_back(data);
int i;
//i 是子节点的下标,(i - 1) / 2 是子节点的父节点的下标
for (i = n - 1; i > 0 && data > nums[(i - 1) / 2]; i = (i - 1) / 2) {
nums[i] = nums[(i - 1) / 2];
}
nums[i] = data;
}
分析:
1.这里的难点是弄清楚头节点的下标,对于下标从1开始的数组,左节点 = 头节点 * 2;对于下标从0开始的数组,左节点 = 头节点 * 2 + 1;
2.一定要注意 i > 0 这个条件;
3. 取出最大值函数
思路:堆顶的值就是最大值,直接取出即可;
int heap::top() {
if (n == 0) {
cout << "没有值,无法返回" << endl;
return INT_MIN;
}
return nums[0];
}
4. 删除最大值【从下往上】
思路:交换第一个值和最后一个值,从头节点开始调整
void heap::pop() {
if (n == 0) {
cout << "没有值,无法删除" << endl;
return;
}
swap(nums[0], nums[n - 1]);
--n;
adjust(0);
}
void heap::adjust(int index) {
int left;
for (; index * 2 + 1 < n; index = left) {
//left 是左节点
left = index * 2 + 1;
//判断右节点是否存在,如果存在判断右节点和左节点谁大
if (left + 1 < n && nums[left] < nums[left + 1])
left++;
if (nums[index] < nums[left])
swap(nums[index], nums[left]);
}
}
分析:
- index 是父节点的下标,左节点的下标为 index * 2 + 1,右节点的下标为 index * 2 + 2;
- 一定要判断右节点是否存在,因为 for 循环的条件是左节点存在;
总结
- 这里的 pop() 并没有将数据从 nums 中删除,仅仅是缩小了 n;
- 因为构造函数中使用了 initializer_list 所以可以使用花括号的初始化;
heap h{10 ,1, 4 , 6 , 2};
int n = h.size();
for (int i = 0; i < n; ++i) {
cout << h.top() << " ";
h.pop();
}