完全二叉树:
如果一棵深度为k的二叉树,1至k-1层都是满的,
即每层结点数满足2i-1,只有最下面一层的结点数小于2i-1,
并且最下面一层的结点都集中在该层最左边的若干位置,
则此二叉树称为完全二叉树。
那么我们所学的二叉堆呢,总的来说就是一个数组。
它可以被看作一棵完全二叉树。树中每个结点与数组中存放该结
点中值的那个元素相对应。如图所示::
设数组 A A A的长度为 l e n len len,二叉树的结点个数为 s i z e size size,
s i z e ≤ l e n ize≤len ize≤len,则 A [ i ] A[i] A[i]存储二叉树中编号为i的结点值
( 1 1 1≤ i i i≤ s i z e size size),而 A [ s i z e ] A[size] A[size]以后的元素并不属于相应的堆,
树的根为 A [ 1 ] A[1] A[1],并且利用完全二叉树的性质,
我们很容易求第 i i i个结点的父结点 f a ( i ) fa(i) fa(i)、左孩子 l c h ( i ) lch(i) lch(i)、
右孩子 r c h ( i ) rch(i) rch(i)的下标,分别是 i / 2 i/2 i/2、 2 i 2i 2i、 2 i + 1 2i+1 2i+1。
除了这一性质之外,同时对除根以外的每个结点i,A[fa(i)]≥A[i]。
即除根结点以外,所有结点的值都不得超过其父结点的值,
这样就推出,堆中最大元素存放在根结点中,
且每一结点的子树中的结点值都小于等于该结点的值,
这种二叉堆又称为“大根堆”;反之,称为“小根堆”。
堆一般有两个重要的操作, p u t put put(往堆中加入一个元素)和
g e t get get(从堆中取出并删除一个元素), p u t put put操作(也可用于建堆,
首先创建一个小根堆为例):
1、在堆尾加入一个元素,并把这个结点置为当前结点。
2、比较当前结点和它父结点的大小
如果当前结点小于父结点,则交换它们的值,并把父结点置为当前结点,
继续转2。
如果当前结点大于等于父结点,则转3。
3、结束。
以此循环 n n n次,即可建立一个小根堆出来。
具体的操作请见其他各大博客,均有提及。
那么现在来看一下建小根堆的代码:
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 15;
int heap[maxn], n, heap_size[maxn];
void PUT(int k){
int fa, now;
heap[++heap_size] = k;//把现在需要插入的数放到堆尾
now = heap_size;//把堆尾的这个数设为当前操作数
while(now > 1){//向上与父节点比较知道比完了各节点结束
fa = now >> 1;
if(heap[now] >= heap[fa])//不小于父节点的值就结束
break;
swap(heap[now], heap[fa]);//交换函数
now = fa;//交换后又继续往上比较
}
}
int main(){
scanf("%d", &n);
for(int i = 1;i <= n;i++){//插入输入
scanf("%d",&a);
PUT(a);
}
for(int i = 1;i <= n;i ++){//输出小根堆
printf("%d", heap[i]);
}
return 0;
}
从堆中去除并删除的
g
e
t
get
get操作:
1、取出堆中根结点的值。
2、把堆的最后一个结点( h e a p s i z e heap_size heapsize)放到根的位置上,把根覆盖掉,堆长度减一。
3、把根结点置为当前父结点,即当前操作结点 n o w now now。
4、如果 n o w now now无儿子( n o w > h e a p s i z e / 2 now>heap_size/2 now>heapsize/2),则转6;
否则,把 n o w now now的两(或一)个儿子中值较小的那一个置为当前子结点 s o n son son
5、比较 n o w now now与 s o n son son的值,如果 n o w now now的值小于等于 s o n son son,转6;
否则交换两个结点的值,把 n o w now now指向 s o n son son,转4。
6、结束。
由于技术问题,本人找不到这张流程图,在这里道歉。
那么就来看一下删除根节点操作的代码吧:
int Get(){
int now, son ,res;
res = heap[1];//保存根节点
heap[1] = heap[heap_size --]// 堆尾节点替换根节点
now = 1;//把根节点置为当前节点开始向下与儿子比较
while(now * 2 <= heap_size){//当前节点非叶子节点就继续
son = now * 2;//初始化左儿子
if(son < heap_size && heap[son + 1]< heap[son])
son ++;//有右儿子,且右儿子比左儿子削,son指向右儿子
if(heap[now] <= heap[son])//如果你爸没你儿子大,结束
break;
swap(heap[now], heap[son]);//不然就交换
now = son;//把你儿子设为你现在的老子,继续向下比
}
return res;//取出原堆中的根节点
}
T1堆排序:
一道简单的小根堆,只需要用 s w a p swap swap交换更小的数,最后用一重
n n n次的循环依次取出并删除根结点就行了,即执行 g e t get get操作即可。
//正常的小根堆
//加上void swap()一个swap
//把那个数放到根节点上
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int heap_size,n;
int heap[10000005];
void swap(int &a,int &b){
int t=a;a=b;b=t;
}
void put(int d){
int now,fa;
heap[++heap_size]=d;
now=heap_size;
while(now>1){
fa=now>>1;
if(heap[now]>=heap[fa]) return ;
swap(heap[now],heap[fa]);
now=fa;
}
}
int Get(){
int now,fa,res;
res=heap[1];
heap[1]=heap[heap_size--];
now=1;
while(now*2<=heap_size){
fa=now*2;
if(fa<heap_size && heap[fa+1]<heap[fa]) fa++;
if(heap[now]<=heap[fa]) return res;
swap(heap[now],heap[fa]);
now=fa;
}
return res;
}
void work(){
int x,y,ans=0;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&x);
put(x);
}
for(int i=1;i<=n;i++){
printf("%d ",Get());
}
}
int main(){
work();
return 0;
}
T2合并果子:
这道题与模板没有区别,首先通过 n n n次操作 p u t put put建立一个小根堆
进而,不断重复提出两个数并删除,再加起来形成一个新节点再
进行同样的 p u t put put操作,三个为一组,共有 n − 1 n - 1 n−1组,最后的答案
是 a n s ans ans。
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int heap_size, heap[10005];
int n;
void swap(int &x, int &y){
int t = x, x = y, y = t;
}
void put(int x){
int son, fa;
heap[++heap_size] = x;
son = heap_size;
while(son > 1){
fa = son >> 1;
if(heap[son] >= heap[fa]) break;
swap(heap[son] ,heap[fa]);
son = fa;
}
}
void get(){
int fa, son, res;
res = heap[1];
heap[1] = heap[heap_size --];
fa = 1;
while(fa * 2 <= heap_size){
son = fa *2;
if(son < heap_size && heap[son + 1] < heap[son]) son ++;
if(heap[fa] <= heap[son]) break;
swap(heap[fa], heap[son]);
fa = son;
}
return res;
}
int main(){
int x, y, ans = 0;
scanf("%d",&n);
for(int i = 1;i <= n;i++){
sacnf("%d",&x);
put(x);
}
for(int i = 1;i <= n;i++){
x = get();
y = get();
ans += x + y;
put(x + y);
}
printf("%d\n",ans);
return 0;
}
T3小猫钓鱼:
首先可以这么假设,如果知道了取到最大值得情况,当人最后在
第 i i i个池塘里钓鱼,那么时间至少是固定的,所以尽可能选取
钓到的鱼最多的池塘。所以,暴力枚举 n n n次鱼塘的位置,即可。
那么有贪心,必定会有动态规划了,假设 o p t [ t ] [ n ] opt[t][n] opt[t][n]表示第 t t t分钟
人在第 i i i个鱼塘里钓到的鱼数。则:
opt[t][n] = maxinum(opt[t - k][n - 1] + s);
暴力枚举 k k k, s s s为 t − k + 1 t - k + 1 t−k+1到 t t t之间,除开从第 n − 1 n - 1 n−1的鱼塘走到
第 n n n个鱼塘的时间,在第 n n n个鱼塘中可以钓到的鱼数。
好吧,进入正题。
建立以 f i s h fish fish为键值的大根堆,包括能钓到鱼的数量和池塘的编号
然后借助枚举创造条件。即可得出正解。
#include <iostream>
#include <cstdio>
using namespace std;
struct Data {
int fish, lake; //堆中结点的信息
};
Data heap[105];
int t[105], f[105], d[105];
int Max, k, t1;
void maintain(int i){ //维护堆
Data a;
int next;
a = heap[i];
next = i * 2;
while (next <= k) {
if (next < k && heap[next].fish < heap[next + 1].fish)
next++;
if (a.fish < heap[next].fish) {
heap[i] = heap[next];
i = next;
next *= 2;
} else
break;
}
heap[i] = a;
}
void work() {
int i, j, m, n;
cin >> n;
for (i = 1; i <= n; i++) cin >> f[i];
for (i = 1; i <= n; i++) cin >> d[i];
for (i = 1; i < n; i++) cin >> t[i];
cin >> m;
for (k = 1; k <= n; k++) {
int Time = m - t1;
int ans = 0;
for (i = 1; i <= k; i++) {
heap[i].fish = f[i];
heap[i].lake = i;
}
for (i = 1; i <= k / 2; i++) maintain(i);
while (Time > 0 && heap[1].fish > 0) {
ans += heap[1].fish;
heap[1].fish -= d[heap[1].lake];
maintain(1);
Time--;
}
if (ans > Max)
Max = ans;
t1 += t[k];
}
cout << Max << endl;
}
int main() {
work();
return 0;
}
这个知识点也是需要将每个函数的具体框架,记牢记熟。
和后面的 S T L STL STL相呼应。