先推荐一篇写的比较好的博客基本数据结构――堆的基本概念及其操作
【定义】
堆是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
【基础操作】
先说一下 c++ 标准库 STL 中有一个神奇东西叫做优先队列(priority_queue),它可以很方便地实现一些基础的堆操作
#include<queue> //头文件
using namespace std;
priority_queue<int>q; //定义一个大根堆
priority_queue<int,vector<int>,greater<int> >q1; //定义一个小根堆
q.push(x); //将x放进堆里
q.top(); //取出堆的堆顶元素,不删除
q.empty(); //判断堆是否为空,为空是true,不为空是false
q.pop(); //删除堆顶元素
不过,虽然有这么好的东西,但我们还是要会手写堆,下面以小根堆为例(具体的操作就不细讲了,大家可以自行百度)
1、将第 i 个元素向上调整
void pushup(int i)
{
while(i/2>=1)
{
if(a[i]<a[i/2])
{
swap(a[i],a[i/2]);
i/=2;
}
else break;
}
}
2、将第 i 个元素向下调整(sum 是堆中元素的总个数)
void pushdown(int i)
{
int x;
while(i*2<=sum)
{
x=i*2;
if(x+1<=sum&&a[x+1]<a[x])
x++;
if(a[i]>a[x])
{
swap(a[i],a[x]);
i=x;
}
else break;
}
}
3、插入元素 x
void push(int x)
{
sum++;
a[sum]=x;
pushup(sum);
}
4、删除堆顶元素
void pop()
{
swap(a[1],a[sum]);
sum--;
pushdown(1);
}
5、取出堆顶元素
int top()
{
return a[1];
}
6、判断堆是否为空(为空是true,不为空是false)
bool empty()
{
if(!sum)
return true;
return false;
}
【可并堆】
以下三种方法时间复杂都是O(log n),还是以小根堆为例
1、斜堆
合并的时候保证 x < y (若 x > y 就交换 x,y),这样的话 x 就是新堆的根,直接递归下去让 x 的右儿子和 y 合并就行了
由于每次是在右儿子处合并,所以交换左右儿子可以适当减小树高
int merge(int x,int y)
{
if(!x) return y;
if(!y) return x;
if(a[x]>a[y]) swap(x,y);
right[x]=merge(right[x],y);
swap(left[x],right[x]);
return x;
}
2、随机堆
emmm,这里唯一不同的就是左右子树是随机更换的,本蒟蒻解释不来。。。
int merge(int x,int y)
{
if(!x) return y;
if(!y) return x;
if(a[x]>a[y]) swap(x,y);
right[x]=merge(right[x],y);
if(rand()%2) swap(left[x],right[x]);
return x;
}
3、左偏树
定义一个节点为外节点当且仅当它没有左右儿子
定义一个节点的 dis 为它到最近外节点的距离(空节点的 dis 为 -1)
左偏树保证左儿子的 dis 始终不小于右儿子的 dis
然后其他的和上面两种差不多
int merge(int x,int y)
{
if(!x) return y;
if(!y) return x;
if(a[x]>a[y]) swap(x,y);
right[x]=merge(right[x],y);
if(dis[left[x]]<dis[right[x]])
swap(left[x],right[x]);
dis[x]=dis[right[x]]+1;
return x;
}
还有一些黑科技堆本蒟蒻也不知道怎么写。。。
不过这些应该已经够了