前言:其实STL中已经定义了有关堆操作的函数:
https://blog.csdn.net/qq_41431457/article/details/88573917
这篇文章主要介绍一下它的原理
一、什么是堆
1、堆是一个二叉树,每个结点都要比两个孩子结点要大或者最小,所以根结点是二叉树中最大或最小的结点
2、因为我们用树状数组来存储,所以堆也是一个完全二叉树,
3、根结点是a[1],左孩子与右孩子结点分别是a[2*n] ,a[2*n+1]
4、大根堆:所有的结点都比它左右孩子结点要大(根结点最大)
5、小根堆:所有的结点都比它的左右孩子结点要小(根结点最小)
二、堆的解题范围
因为堆是只能保证根结点最大或最小,所以题目中如果只需要求一个整个序列里最小或者最大的一个值,就可以考虑用堆结构处理
三、基本操作(以大根堆为例子)
1、向下维护堆,从一个节点开始,如果左右孩子比它要大,那么选一个大的孩子结点与之交换,在往下维护
void down(int x)
{
int s=p<<1;
while(s<=size)
{ //下面这句话是从左右儿子中选一个更大的做交换
if(s+1<=size&&heap[s+1]>heap[s]) s++;
if(heap[s]>heap[p])
{
swap(heap[s],heap[p]);
p=s; s=p<<1; //转移到大的那个孩子结点
}
else break;
}
}
2、向上维护堆,同上,只不过顺序颠倒过来了
void up(int x)//二叉小根堆向上调整(子节点小于父节点就调整)
{
while(x>1)
if(heap[x]>heap[x/2])//如果此节点比父结点大的话
{
swap(heap[x],heap[x>>1]);//交换
x>>=1;//转移到该节点的父节点
}
else break;
}
3、插入堆,先将插入的元素放到堆底,在向上调整堆
void insert(int val)//二叉堆插入,新元素放在堆底,向上调整
{
heap[++size]=val;//放到堆的末尾
up[size];//从底部往上维护堆
}
4、取出堆顶(将堆顶放到堆地,堆底上移)并维护堆
int extract() //删除堆顶并返回
{
swap(heap[1] , heap[size--]);//将堆顶移至堆底,向下调整
down(1);//从堆顶往下维护
return heap[size+1] ;
}
四、堆排序
假设序列已经是一个堆,我们将根结点取出,用最后一个结点补上,此时,堆结构被破坏,我就需要调整剩下的结点构成的二叉树,重新将它调整为堆
步骤为:
- 初建堆,将原序列序列建成一个堆结构
- 取出堆顶(根结点),用最后一个结点补上堆顶
- 调整这个棵树,使之重新为堆
- 重复2-3步,直到所有的结点都被取出
代码:
#include<bits/stdc++.h>
#define M 10000
using namespace std;
int heap[M],size,n;//定义树状数组,数组长度
void up(int x)//二叉小根堆向上调整(子节点小于父节点就调整)
{
while(x>1)
if(heap[x]>heap[x/2])//如果此节点比父结点大的话
{
swap(heap[x],heap[x>>1]);//交换
x>>=1;//转移到该节点的父节点
}
else break;
}
void down(int x)
{
int s=p<<1;
while(s<=size)
{ //下面这句话是从左右儿子中选一个更大的做交换
if(s+1<=size&&heap[s+1]>heap[s]) s++;
if(heap[s]>heap[p])
{
swap(heap[s],heap[p]);
p=s; s=p<<1; //转移到大的那个孩子结点
}
else break;
}
}
void insert(int val)//二叉堆插入,新元素放在堆底,向上调整
{
heap[++size]=val;//放到堆的末尾
up[size];//从底部往上维护堆
}
int extract() //删除堆顶并返回
{
swap(heap[1] , heap[size--]);//将堆顶移至堆底,向下调整
down(1);//从堆顶往下维护
return heap[size+1] ;
}
int main()
{
cin>>n;
n=size;
for(int i=n/2;i>=1;i--)//初建堆,从下往上维护
down(i);
for(int i=1;i<n;i++)//每次将堆顶放到最后,再调整堆
extract();
//此时有序
}