堆 (Heap)是 计算机科学 中一类特殊的数据结构的统称。 堆通常是一个可以被看做一棵 完全二叉树 的数组对象。 堆(heap)是计算机科学中一类特殊的数据结构的统称。 堆通常是一个可以被看做一棵树的数组对象。 堆总是满足下列性质: 堆总是一棵完全二叉树。 将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆。
今天学的是小根堆。
小根堆的节点都小于左右子节点。倘若设根节点为x,那么左儿子就为2x,右儿子就为2x+1;
关于堆的基本算法有两种:1.向上调整 2.向下调整。
我们用size来表示位置,用数组heap来表示堆
以下为代码的实现
1.向上调整
void up(int u)//u为数在堆中的位置
{
while(u/2&&h[u/2]>h[u])//确保节点有效并判断父节点是否大于子节点
{
swap(h[u/2],h[u]);//若大于则交换。
u/=2;
}
}
2.向下调整
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;//先判断左右子节点是否小于根节点并给t以u的左or右子节点的位置
if(u!=t)//判断
{
swap(h[u],h[t]);//交换位置
doun(t);//向下递归
}
}
根的基本操作有五种
1.插入
2.求集合中的最小值
3.删除最小值
4.删除任意一个元素
5.修改任意一个元素
在这五种基本操作之前,先学习如何建立一个堆
for(int i=1;i<=n;i++) scanf("%d",&h[i]);//n为个数
size=n;
for(int i=n/2;i;i--) down(i);//堆的建立
将当前待排序的n个记录为作为一个大小为n的一维数组,然后向下取整i=n/2开始(即为最后一个非叶节点)
这五种基本操作都可以用上文的向上向下的代码实现
1.插入:
heap[++size]=x;
up(size);
先将x插入堆的最底部,向上调整。
2.求最小值
因为小根堆的定义就是根节点为最小
所以最小值就是heap[1];
3.删除最小值
上点表示了最小值就是heap[1],如果直接删除根节点,维护堆的过程繁琐,但是若是删除最底部的节点却很简单,于是我们可以先将底部节点的值赋予根节点代替根节点的值,再删除底部节点,将根节点向下调整,代码的表现如下
heap[1]=heap[size];
size--;
down(1);
4.删除任意一个数
方法于上点大同小异,不过要判断是向上调整还是向上调整或者是保持原样,解决这个问题的方法也很简单,见代码
heap[k]=size;
size--;
down(k),up(k);
5.修改任意一个元素
对于修改后的数值是向上向下调整还是保持原样,解决的方法如上点一般
heap[k]=x;
down(k),up(k);
了解了五种基本操作的代码,接下来看一道例题
输入一个长度为 nn 的整数数列,从小到大输出前 mm 小的数。
输入格式
第一行包含整数 nn 和 mm。
第二行包含 nn 个整数,表示整数数列。
输出格式
共一行,包含 mm 个整数,表示整数数列中前 mm 小的数。
数据范围
1≤m≤n≤1051≤m≤n≤105,
1≤数列中元素≤1091≤数列中元素≤109
输入样例:
5 3
4 5 1 3 2
输出样例:
1 2 3
实现的代码如下
#include<iostream>
using namespace std;
int n,m;
int h[1000010],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);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&h[i]);
size=n;
for(int i=n/2;i;i--) down(i);
while(m--)
{
printf("%d ",h[1]);
h[1]=h[size];
size--;
down(1);
}
return 0;
}
具体各部分的代码块都在上面展示过了。
理解了堆之后我们就可以用堆来排序
在各种的排序手段中,堆排序的优点是在于大基数的情况下,时间复杂度较小,优势较大。
在对堆的学习中我认为最重要的就是理解向上向下调整的基本代码和如何建立一个堆。