堆知识梳理
堆知识梳理
堆的特点
1.堆是一棵完全二叉树,所以除了根节点和最后一个左子结点可以没有兄弟结点,其它结点都必须有
2.根节点中的数要么是堆中的最大数(大根堆),要么是堆中的最小数(小根堆)
大根堆:
小根堆:
堆的存储
我们在前面提到过,堆是一棵完全二叉树,所以它是用数组存储的,根节点存在下标为1的位置,此后每个结点(在n处)的左子节点存在2n处,右子节点存在2n+1处
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
数字 | 95 | 38 | 45 | 11 | 20 | 33 | 27 |
手写堆
虽然接下来的内容有点复杂,但还是希望大家自己手打一边,手打堆可以发掘出堆的更多用法
加入数据
首先,我们有一个数据52,然后,我们有一个伟大的决定,将它加入大根堆(小根堆)
下标 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
数字 | 84 | 55 | 32 | 26 | 42 | 12 |
然后,我们将新数据放在树(数组)的末尾
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
数字 | 84 | 55 | 32 | 26 | 42 | 12 | 52 |
最后,我们将它与它的父亲比较,如果它大于(小于)它的父亲,那么交换,重复这一系列动作,直到它的父亲大于(小于)它为止
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
数字 | 84 | 55 | 52 | 26 | 42 | 12 | 32 |
代码
void put(int n){//大根堆代码,n为加入的值
int now,nex;//now为现在的下标,nex为heap[now]的父亲
heap[++heap_size] = n;//把n加到数组的最后
now = heap_size;
while(now > 1) {
nex = now / 2;
if(heap[now] <= heap[nex]) break;
swap(heap[now],heap[nex]);//交换heap[now]和heap[nex]
now = nex;
}
}
void put(int n){//小根堆代码,n为加入的值
int now,nex;//now为现在的下标,nex为heap[now]的父亲0
heap[++heap_size] = n;//把n加到数组的最后
now = heap_size;
while(now > 1) {
nex = now / 2;
if(heap[now] >= heap[nex]) break;
swap(heap[now],heap[nex]);//交换heap[now]和heap[nex]
now = nex;
}
}
弹出数据
堆只支持对堆顶进行弹出,所以弹出后我们还需要继续维护堆
弹出后,我们的堆会变成两个堆,依旧有序
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
数字 | 55 | 52 | 26 | 42 | 12 | 32 |
我们将这两个堆的堆顶中大的(小的)那个拿来做堆顶
下标 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
数字 | 55 | 52 | 26 | 42 | 12 | 32 |
我们可以发现,被那去做堆顶的那个数据的所属堆的堆顶不见了(如上面表格下标为2的空位)
那么我们重复做上面的步骤直到移到叶子结点
下标 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
数字 | 55 | 52 | 42 | 12 | 32 | 26 |
代码
int pop(){//大根堆代码
int now = 1,nex,res = heap[1];//now为现在的下标,nex为heap[now]的父亲,res为堆顶
heap[1] = heap[heap_size --];
while(now * 2 <= heap_size){
nex = now * 2;
if(nex < heap_size && heap[nex + 1] < heap[nex]) nex ++;
if(heap[now] >= heap[nex]) break;
swap(heap[now],heap[nex]);
now = nex;
}
return res;
}
int pop(){//小根堆代码
int now = 1,nex,res = heap[1];//now为现在的下标,nex为heap[now]的父亲,res为堆顶
heap[1] = heap[heap_size --];
while(now * 2 <= heap_size){
nex = now * 2;
if(nex < heap_size && heap[nex + 1] < heap[nex]) nex ++;
if(heap[now] <= heap[nex]) break;
swap(heap[now],heap[nex]);
now = nex;
}
return res;
}
STL
STL是c++自带的,操作简便,但过于死板,堆是由优先队列实现的
定义
优先队列的排序顺序不是以到达的先后顺序排序,而是按照其优先级排序,与堆相同,所以用其来实现堆
优先队列属于
#include<queue>
定义
priority_queue <int,vector<int>,greater<int> >q1;//小根堆
priority_queue <int,vector<int>,less<int> >q2;//大根堆
priority_queue <int>q3;//大根堆
注意:q1,q2定义时<int> >q1的空格
拓展:vector为STL中的动态数组
基本操作
q.empty();//如果队列为空,返回1,否则返回0
q.size();//返回队列中元素的个数
q.pop();//删除队首元素,无返回值
q.top();//返回队首元素
q.push(x);//让x入队
q.back();//返回队尾元素
q.emplace();//造一个数据插入队中 (c++11新特性)
对顶堆
是由一个大根堆和一个小根堆组成的,用于求第k大
思想
我们建一个大根堆,一个小根堆
其中一个限制它大小为k,如果一个数据大于(小于)这个堆的顶,那么存入,否则存入另一个堆
这样最后限制大小的堆的堆顶就是最终答案
我们来看一道题 The kth great number(对顶堆)
完结撒花
欢迎大家留言
小编蒟蒻一个,有什么问题请大佬不惜赐教Orz