0 简介
堆是一种比较精妙的数据结构,一般所使用的二叉堆,其本质上来说,是一棵完全二叉树,所以一般使用一个数组就可以模拟堆的各种操作。对于任意一个节点来说,若它的节点编号为 i i i( 1 < = i < = n 1<=i <= n 1<=i<=n),那么它的左儿子节点编号就是 2 i 2i 2i,右儿子节点编号就是 2 i + 1 2i+1 2i+1,父亲节点就是 i / 2 i/2 i/2。最小堆中,每一个节点的值均小于父亲节点,大于它的儿子节点。堆一般支持插入元素、返回和删除最值元素等操作,下面以二叉最小堆为例讲讲这两个操作。
1 堆的操作
1.1 插入元素
在堆中插入一个元素很简单,对于一个新元素x,若当前堆的大小为size,首先将元素放入数组末端,h[++size] = x;
,然后执行上滤操作即可。上滤的操作如下,也就是说如果它的父亲节点大于它的话已知向上交换元素即可。
void up(int u)
{
while (u / 2 && h[u] < h[u / 2])
{
swap(h[u], h[u / 2]);
u /= 2;
}
}
1.2 删除最值元素
最小堆的第一个元素总是最小元素,所以删除最值元素的思路是这样的,令数组的第一个元素等于最后一个元素,若当前堆的大小为size,size–,然后对第一个元素进行下滤操作即可。所谓下滤操作,如果发现当前节点大于它的儿子节点,就将它和子节点交换,直到它达到正确的位置。代码如下所示。
void down(int u)
{
int t = u;
if (u * 2 <= size && h[u * 2] < h[t]) t = u * 2;
if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
if (u != t)
{
swap(h[u], h[t]);
down(t);
}
}
1.3 建堆
一种常见的建堆思路是不断将数组元素插入一个堆中,这样的操作最坏情况下算法复杂度将达到 n l o g n nlogn nlogn。有一种 o ( n ) o(n) o(n)的建堆算法如下所示,只需从 n / 2 n/2 n/2处开始,从后往前不断下滤即可。
for (int i = n / 2; i; i -- )
down(i);
2 堆排序
根据前面所说的三种操作,我们就可以完成堆排序了。对于输入的数组,我们首先建堆,然后循环输出最小元素,删去最小元素即可。以acwing838题为例。题目要求如下:
实现代码如下:
#include <stdio.h>
#include <algorithm>
using namespace std;
int mySize;
const int N = 1e5+10;
int nums[N];
void down(int i)
{
int d = i;
if(2*i <= mySize && nums[d] > nums[2*i]) d = 2*i;
if(2*i+1 <= mySize && nums[d] > nums[2*i+1]) d = 2*i + 1;
if(i != d)
{
swap(nums[i],nums[d]);
down(d);
}
}
int main(void)
{
int n,m;
scanf("%d%d",&n,&m);
mySize = n;
for(int i = 1; i <= n; i++)
scanf("%d",&nums[i]);
for(int i = n/2; i ; i--)
down(i);
for(int i = 1; i <= m; i++)
{
printf("%d ",nums[1]);
nums[1] = nums[mySize--];
down(1);
}
return 0;
}