二叉堆是一种数组对象,它可以被视为一棵完全二叉树。树中每个结点与数组中存放该结点中值的那个元素相对应,如图。
设数组 A A A的长度为 l e n len len,二叉树的结点个数为 s i z e size size, s i z e ≤ l e n size≤len size≤len,则 A [ i ] A[i] A[i]存储二叉树中编号为 i i i的结点值( 1 ≤ i ≤ s i z e 1≤i≤size 1≤i≤size),而 A [ s i z e ] A[size] A[size]以后的元素并不属于相应的堆,树的根为 A [ 1 ] A[1] A[1],并且利用完全二叉树的性质,我们很容易求第 i i i个结点的父结点、左孩子、右孩子的下标,分别是 i / 2 i/2 i/2、 2 i 2i 2i、 2 i + 1 2i+1 2i+1。(自己看图理解)
大根堆具有这样一个性质:对除根以外的每个结点i,A[fa(i)]≥A[i]。即除根结点以外,所有结点的值都不得超过其父结点的值,这样就推出,堆中最大元素存放在根结点中,且每一结点的子树中的结点值都小于等于该结点的值,这种二叉堆又称为“大根堆”;
反之,称为“小根堆”。
假如这是原始的堆
现在我们要插入一个13的节点,首先将他接在二叉树的后面,即7的右儿子
可以发现他与他的父节点不满足大根堆的关系,所以交换他们
发现现在它和它的父节点仍然满足大根堆的性质,所以插入完成
经过验证,新二叉堆依然满足性质,证明插入没问题
转化为文字语言是:
接下来是代码实现:
1.在堆尾加入一个元素,并把这个结点置为当前结点。
heap[++len]=a;
其中len为堆的元素个数
while(now>1){
int fa=now>>1;
if(heap[fa]<heap[now]){
swap(heap[fa],heap[now]);
now=fa;
}
else{
break;
}
}
完整代码:
void Put(int a){ //加入节点
heap[++len]=a;
int now=len;
while(now>1){
int fa=now>>1;
if(heap[fa]<heap[now]){
swap(heap[fa],heap[now]);
now=fa;
}
else{
break;
}
}
}
这是大根堆的实现,小根堆就请大家自己通过理解改一下了~
二叉堆主要支持的是删除根节点
依然是来几张图理解一下
这是原始的堆
但是这样就变成两个堆了,怎么办呢?我们把最后一个节点,也就是13置为堆顶
现在让13像插入时的那样,只是向下变为向上,一一比较儿子节点两(或一)个儿子中值较大的与当前交换
再看看这个堆,是不是就正确了呀
(现在说的和代码都为小根堆实现(毕竟插入用的大根堆,本着公平的原则,用用小根堆))
1、根据需要取出堆中根结点的值。
2、把堆的最后一个结点(heap_size)放到根的位置上,把根覆盖掉, 堆长度减一。
heap[1]=heap[len];
len--;
3、把根结点置为当前父结点,即当前操作结点now。
int now=1;
4、如果now无儿子(now>heap_size/2),则转6;否则,把now的两(或一)个儿子中值较小的那一个置为当前子结点son。
while(now<=len/2){
int son;
if(now*2<len && heap[now*2]>heap[now*2+1]){
son=now*2+1;
}
else{
son=now*2;
}
5、比较now与son的值,如果now的值小于等于son,转6;否则交换 两个结点的值,把now指向son,转4。
if(heap[now]<heap[son]){
break;
}
else{
swap(heap[now],heap[son]);
now=son;
}
}
6、结束。
void Get(){
heap[1]=heap[len];
len--;
int now=1;
while(now<=len/2){
int son;
if(now*2<len && heap[now*2]>heap[now*2+1]){
son=now*2+1;
}
else{
son=now*2;
}
if(heap[now]<heap[son]){
break;
}
else{
swap(heap[now],heap[son]);
now=son;
}
}
}
堆是一种时间复杂度为 l o g log log级的数据结构,主要用来维护一个序列的最值以及删除最值、插入元素并维护最值,堆在NOIP竞赛中应用广泛,常用与快速查询最大(最小值),优化各种算法(如:最短路算法、DP算法),是一种效率高,应用广泛的数据结构。