二叉堆是一种基础的数据结构,它的操作相较于其他的数据结构(如栈,队列)操作较为有限,一般为插入,删除,查询等。
二叉堆的性质
堆是什么?
- 它是一种特殊的完全二叉树。
- 根节点为优先度最大的数据,根节点的子节点优先度稍微低于根节点,以此类推,越往下优先度越低。优先度不一定为数学意义上的大小,根据实际情况选择。
- 基础的有两种堆,根节点值最大的堆称为大根堆,反过来,根节点最小的称为小根堆。
堆的操作
建立堆
堆因为是一种特殊的完全二叉树,我们为了简便,通常可以用数组存储。
for(i=0;i<num;i++)
scanf("%d",&h[i]);
此时数组中是杂乱无章的数,此时我们需要通过向上或向下比较来使堆成为一个小根堆或大根堆。
向下调整函数
void siftdown(int i)//传入一个需要调整的节点编号
{
int flag=0,t;//flag用来标记是否需要调整
//当i至少有左儿子,并且需要调整时,执行循环
while(i*2<n&&flag==0)
{
//与左儿子进行比较,记录小的数的下标
if(h[i]<h[2*i])
t=i;
else
t=2*i;
//如果有右儿子,再将上面比较过的小的数与右儿子进行比较
if(i*2+1<n)
{
if(h[t]>h[i*2+1])
t=2*i+1;
}
//如果t!=i,我们就交换t与i的位置
if(t!=i)
{
swap(h[t],h[i]);
i=t;
}
else
flag=1;
}
return ;
}
我们通过改变I的下标,就可以求出一个小根堆。
void creat()
{
int i;
for(i=n/2-1;i>=0;i--)
siftdown(i);
return;
}
这样我们就建立了一个小根堆。
堆排序
建立了小根堆之后,如何进行排序呢。
很简单我们只需要不断输出根节点的值,并不断向上调整,就可以了,这也是不断删除的操作,在输出之后删除。
int deletemax()
{
int t;
t=h[0];//将根节点值保存
h[0]=h[n-1]; //将大的数赋值到根节点
n--;
siftdown(0);//调整为小根堆
return t;
}
例题
例题链接
这个题嘛,是个水题,有很多解法,比如我们可以贪心一直找两个最小的相加,比如我们可以用二叉堆,再比如我们可以用stl里的优先队列。
但是我们今天是学二叉堆,就写写二叉堆的写法。
#include <iostream>
#include <cstdio>
using namespace std;
int fru[10010],n,i;
void siftdown(int i)//向下调整函数
{
int flag=0,t;
while(i*2<n&&flag==0)
{
if(fru[i]<fru[2*i])
t=i;
else
t=2*i;
if(i*2+1<n)
{
if(fru[t]>fru[i*2+1])
t=2*i+1;
}
if(t!=i)
{
swap(fru[t],fru[i]);
i=t;
}
else
flag=1;
}
}
void siftup(int i)
{
int flag=0;
if(i==0)
return;
while(flag=0&&i!=0)
{
if(fru[i]<fru[i/2])
swap(fru[i],fru[i/2]);
else
flag=1;
i=i/2;
}
}
void creat()
{
for(i=n/2-1; i>=0; i--)
siftdown(i);
}
int deletemax()
{
int t;
t=fru[0];
fru[0]=fru[n-1];
siftdown(0);
n--;
return t;
}
int main()
{
while(cin>>n)
{
int sum=0;
for(i=0; i<n; i++)
cin>>fru[i];
creat();
while(n>1)
{
int x=deletemax();
int y=deletemax();
sum=sum+x+y;
n++;
fru[n-1]=x+y;
siftup(n-1);
}
cout<<sum<<endl;
}
}