堆
堆的性质
堆可以认为是一种条件优先的队列,相较于普通的队列,堆每次出队时的元素为整个队列中满足某种极端条件的元素,如最大值(大顶堆),最小值(小顶堆)。
堆的存储结构为一棵完全二叉树,其满足这样一个性质。对于每个节点,其左右子树上的元素均小于(大于)该节点上的元素。这样就可以保证根节点上的元素为整棵树中的元素最小值(最大值)。由于堆使用完全二叉树存储,因此可以用数组来进行堆的实现。
对于堆中的每一个节点,最基本的操作只有两个
-up,即向上调整操作
-down,即向下调整操作
每次up或者down操作的最坏时间复杂度都为O(logn),即取决于二叉树的层数
方便起见,我们让二叉树下标从1开始,这样的话,下标为i的节点的左孩子为2i,右孩子为2i+1,其父节点为i/2
下面以小顶堆为例给出代码实现。其中参数为堆节点的下标
void up(int u)
{
while (u / 2/*非根节点*/ && h[u] < h[u / 2]) //若父节点比该节点大,说明该节点应上移,即与父节点交换
{
swap(h[u], h[u / 2]);
u >>= 1; // u/2,更新当前节点到其父节点
}
}
void down(int u)
{
int t = u; //t存储该节点及其左右孩子节点中最小值的下标
if(u * 2 <= len && h[u * 2] < h[t]) t = u * 2;
if (u * 2 + 1 <= len && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
if (u != t) //若最小并非该节点上的值,交换该节点与其孩子节点的值,并继续向下调整交换后的值,直到不需调整
{
swap(h[u], h[t]);
down(t);
}
}
堆的插入以及删除操作如下
void insert(int x)
{
h[++len] = x;//插入到堆的最后
up(len);//从该最后节点向上调整
}
int pop()
{
int item = h[1];//保存堆顶元素
h[1] = h[len--];//将堆中的末尾元素搬到根节点上,相当于删除了根节点
down(1);//从根节点开始向下调整
return item;
}
最后给出一个在O(n)时间复杂度内将给定数组建成堆的方案。(若一个一个插入建堆,时间复杂度为O(nlogn))
void build_heap(int len)
{
for (int i = len / 2; i ; i--)//从二叉树的第二层开始逐个节点向下调整
{
down(i);
}
}
最后给出堆的STL实现,类名为priority_queue(优先队列类),需要包含头文件queue
#include <queue>
int main()
{
int x;
priority_queue<int> h;//建立一个空堆,默认为大根堆
h.push(x);//向堆中插入一个数
h.top();//返回堆顶的数但不删除
h.pop();//删除堆顶的数,无返回值
h.size();//返回堆的规模
return 0;
}
若想使用小根堆存储数据x,可以使用大根堆存储-x,或者使用下述方法构造小根堆
priority_queue<int,vector<int>,greater<int>> h