做项目的时候听说了最大最小堆这种数据结构,于是就来学习一下。
最大最小堆算是二叉堆的变形,结构与二叉堆类似,只是节点的排列顺序有所不同。相较于二叉堆,最大最小堆能够同时支持最大值或最小值的查询( O ( 1 ) O(1) O(1))和删除( O ( log n ) O(\log n) O(logn))操作。
相较于用两个二叉堆或用平衡树实现相同的功能,最大最小堆唯一的优势大概就是只需要开一个大小为 n n n 的数组。
性质
最大最小堆的结构和二叉堆一样,都是一棵满二叉树。若令根节点深度为 0 0 0,则满足在以深度为偶数的节点为根的子树中,根节点为子树的最小值,否则为子树的最大值。上述条件等价于,深度为偶数的点小于父亲且大于祖父,深度为奇数的点大于父亲且小于祖父(假设键值两两不同)。
那么根节点就是最小值,根节点两个儿子的较大值就是最大值。
操作
主要的操作分为上移和下移,时间复杂度均为 O ( log n ) O(\log n) O(logn)。
min_shift_down(index)
函数在假设编号为 index
的节点的子树中,除根以外均满足最大最小堆的性质,且根节点的深度为偶数,并把根节点下移调整来满足性质。
void min_max_heap::min_shift_down(int index)
{
if (!has_child(index)) return; //若没有孩子就退出
//将根节点调整为它和两个孩子中的较小值,调整后子树仍满足性质
int c = min_child(index);
if (data[c] < data[index]) swap(c, index);
//根据孙子情况讨论
if (!has_grandchild(index)) return;
int grand = min_grandchild(index);
if (data[index] > data[grand]) swap(index, grand), min_shift_down(grand);
}
min_shift_up(index)
函数在假设整个堆若不看编号为 index
的节点,均满足最大最小堆的性质,且 index
的深度为偶数,并把 index
上移调整来满足性质。
void min_max_heap::min_shift_up(int index)
{
if (!has_parent(index)) return; //若没有父亲则退出
if (!has_grandparent(index)) //若没有祖父,则只需要让父亲满足条件
{
int p = parent(index);
if (data[p] < data[index]) swap(p, index);
}
else //否则需要根据祖父情况进行讨论
{
int grand = grandparent(index);
//若祖父不满足性质,则交换后递归处理
if (data[index] < data[grand]) swap(index, grand), min_shift_up(grand);
else
{
int p = parent(index);
//若父亲不满足性质,则交换后递归处理
if (data[index] > data[p]) swap(index, p), max_shift_up(p);
}
}
}