堆,一种数据结构,实际上是一棵完全二叉树,可以保证在正常添加、删除元素的同时,保证对于二叉树中的每个节点,其左右子节点的优先级均低于自身,这样,树根的元素的优先级永远最高。等价于一个优先级队列,在正常增删元素的情况下,保证队首元素永远是优先级最高的那一个。
堆的增添删除元素的时间复杂度均为O(logn),而查询优先级最高元素的时间为O(n)。
什么是一棵完全二叉树呢?百度百科对其的描述如下:
一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。
它有一个重要的性质:树中每个节点的位置可以与一个向量数组的下标一一对应。我们将根节点序号设置为1,那么对任一个编号为i的一个节点,它的父节点序号为i/2,而左子节点的序号为2i,右子节点的序号为2i+1 。正因如此,堆的内部数据结构可以用一个Vector存储数据。
那么,如何维护这样的一课树使得其满足要求呢?堆最重要的数据操作为增添/删除栈顶元素,分别如下进行:
1、元素的增添
首先,对于一个堆,我们默认它已经满足了堆的一切特性,即对于任意节点,其优先级均大于任意一个子节点的(对于子树中的任意一个节点都满足)。现在,我们在序列尾部插入一个新元素,堆的特性就被破坏了,如何对其进行维护呢?
首先,我们将末尾元素作为当前节点,并将其与父节点比较。若此时发生错序,即当前节点的优先级高于父节点,我们将其进行对换。以此类推,直到顺序正确或到达根节点。由于对于一棵节点数量为n的完全二叉树,其高度不会超过log2n,因此我们进行一次回溯所需的时间复杂度为O(logn)。
2、堆顶元素的删除
当我们删除堆顶元素时,缺省的元素操作起来较麻烦。因此一种更实际的做法是,我们将堆顶元素与最后一个元素交换,再将交换后的堆的最后一个元素删除。此时,堆顶元素的顺序被打乱,堆不能维持原来的性质,需要进行调整。我们将堆顶元素作为当前元素,并比较其与优先级最高的子节点的优先级大小(否则,若我们将该节点与优先级较小的子节点交换,那么优先级更大的那个节点将变为其优先级较小兄弟的子节点,依然不符合堆的性质),若发生乱序即当前节点的优先级更小,那么我们交换这两个节点,直到符合堆的性质或不存在子节点为止。同1,堆进行堆顶删除的时间复杂度为O(logn)。
3、堆(优先级队列)的STL接口
堆在stl中的名称为priority_queue,需要#include <queue>来进行引用。
其构造方法有两种
(1)priority_queue<T>,此时要求T存在比较运算符bool operator<(const T) const
(2)priority_queue<T,容器container(默认为vector<T>),比较函数cmp(默认为大顶堆,即less<T>,注意当改用小顶堆less<T>时,尖括号和结尾的尖括号需要加一个空格以防被识别为>>运算符) >
主要接口有push(x)插入元素,pop()删除堆顶元素,返回值为void,以及top()返回堆顶元素的值。