堆:和队列还有栈,一样是一种基础的数据结构。但是栈和队列是线性的数据结构。而堆是一棵树,并且是一个完全二叉树,不仅如此。这颗二叉树还有一些小特殊。举一个例子把。
堆(特殊的完全二叉树):观察这棵树,我们会发现所有的父节点都比子节点要小。符合这样的特点的完全二叉树我们称之为最小堆。反之。如果所有父节点都比子节点要大,这样的二叉树我们称之为最大堆。
知道了一些基本概念。我们来考虑一些问题。
最小堆的堆顶是不是一个这些数字的最小值。很明显是的。而且最小堆的各个子树也是最小堆。因为每个子节点都没父亲节点大,所以堆顶一定是最小值。
现在在考虑一个问题。如何维护这个最小堆呢。
那我们想象一下一个操作:把这些数字中最小数字的删除。然后加入一个值23。在求出最小值。
若用遍历的方法的话。从头到尾遍历一边删除最小的。然后加入23再遍历一边。n方的复杂度。
那用堆的话我们只需要把堆顶元素删除,然后把23放到堆顶。然后不断让23和自己子节点比较大小。若比子节点大和较小的子节点交换位置。就可以了。看一下过程
我们会发现这些竟然只用3步操作。比起n²的复杂的要快太多了!!!。顺带提一下这个是logn的时间复杂度。
说了这么多,再看一下代码实现。
void siftdown(int i){
int t,flag = 0;
while(i * 2 <= n && flag == 0){
if(h[i] > h[i*2])
t = i*2;
else
t= i;
if(i*2+1 <= n)
if(h[t] > h[2*i+1])
t =2*i+1;
if(i != t){
swap(t,i);
i = t;
}
else
flag = 1;
}
return ;
}
代码也很短,也很好理解,就不解释。
在考虑一种操作:我们向最小堆加入一个元素3,该如何实现。这个和上一部的操作有点不同。这次没有要删除最小的数字。这不就没办法从放入堆顶了么?该怎么办?
放不了堆顶我们放到最后最后不就行了,然后和自己位置的父节点比较,若比父节点大就交换值。(思考一下为什么不用和自己的兄弟节点比较。因为父亲节点一定比子节点大,若你比父亲节点大,那你一定比兄弟节点大,是不是?)。这里就不画图描述过程了。
看一下代码。
void siftup(int i){
int flag = 0;
if(i == 1)
while(i != 1 && flag == 0){
if(h[i] < h[i/2])
swap(i,i/2);
else
flag = 1;
i = i / 2;
}
return ;
}
代码一个比一个短,是不是特别简单。
知道了如何维护最小堆了。我们还有一个重要的问题,就是如何建堆。
这时候,比较机智的同学会说。那我们依次把要压进最小堆的数,放到最后,然后通过之前的siftup函数向上调整。
完全O98K!这是一种可行的方法。
那我们看一下代码实现过程:
int n = 0;
for(int i = 1 ; i <= m ; i ++){
n ++;
cin >> x;
h[n] = x;
siftup(n);
}
简短。
分析一下这个的复杂度,是nlogn的。
那有么有更简单的一点的呢。有!
我们直接把数字存入一个一维数组(我们二叉树的就是用数组模拟的)。然后我们从最后一个节点开始,依次判断以这个节点的为根的子树是不是最小堆。然后若所有的子树都是最小堆。那么整棵树就是最小堆了。
这个是有点类似于贪心的思想。一点点递推。
不懂没关系看下代码:
void creat(){
for(int i = n/2 ; i >= 1 ; i--)
siftdown(i);
return ;
}
是不是特别惊喜,特别意外!!,这代码竟然一个比一个短!!!。
两种方法二选一就行了,不过后者的代码的时间复杂度是n,线性的时间,就很快,也更酷!
现在再看一下,能解决那些问题
因为最小堆的能维护最小值。所以堆排序是完全可行的,看代码把:
#include<cstdio>
int h[101];
int n;
void swap(int x,int y){
int t;
t = h[x];
h[x] = h[y];
h[y] = t;
return ;
}
void siftdown(int i){
int t,flag = 0;
while(i * 2 <= n && flag == 0){
if(h[i] > h[i*2])
t = i*2;
else
t= i;
if(i*2+1 <= n)
if(h[t] > h[2*i+1])
t =2*i+1;
if(i != t){
swap(t,i);
i = t;
}
else
flag = 1;
}
return ;
}
void creat(){
for(int i = n/2 ; i >= 1 ; i--)
siftdown(i);
return ;
}
int deletemax(){
int t;
t = h[1];
h[1] = h[n];
n--;
siftdown(1);
return t;
}
int main(){
int i,num;
scanf("%d",&num);
for(int i = 1; i <= num ; i ++)
scanf("%d",&h[i]);
n = num ;
creat();
for(int i = 1; i <= num ; i ++)
printf("%d ",deletemax());
return 0;
}
最后说一些,堆实现起来比较麻烦。虽然代码短,但是也麻烦,大家也可以学习一下STL优先队列的实现。
以上教学出自博主对哈啊算法上堆教学的总结。若有不对可以私聊博主。