预备知识:
完全二叉树
所有结点深度相同,且所有内部结点都有两个子结点的二叉树称为完全二叉树(Complete Binary Tree)。同时,二叉树的结点深度最大差距为1,最下层叶子都集中在该层最左边的若干位置上,这种二叉树也是(近似)完全二叉树。
设结点数为n,那么完全二叉树的树高则为。利用这性质,我们可以高速地管理数据。
二叉堆
如果一个完全二叉树各节点键值与一数组的各元素具有对应关系,那么这个二叉树就是二叉堆。
二叉堆的大小为H,那么二叉堆元素就存储在A[1...H]中,其根的下标为1。当给定一个点的下标i时,可以通过、、+1轻松算出父结点、左子结点、有子结点。
int parent(int i){return i/2;}
int left(int i){return i*2;}
int right(int i){return i*2+1;}
二叉堆中存储的各结点键值需保证堆具有以下性质之一:
- 最大堆性质:结点的键值小于等于父结点的键值
- 最小堆性质:结点的键值大于等于父结点的键值
注:这里只有父子之间既有大小关系,兄弟之间并无限制
最大/最小堆
maxHeapify(i)会将A[i]的值一直向下移动,直到满足最大堆(自下而上),保证每次做的时候i的左右子树都已经是最大堆了。
void maxHeapify(int i){
int l=i*2;
int r=i*2+1;
int largest;
//从左右子结点中找最大
if(l<=H&&A[l]>=A[i])
largest=l;
else
largest=i;
if(r<=H&&A[largest]<A[r])
largest=r;
//如果结点更大
if(largest!=i){
swap(A[largest],A[i]);
maxHeapify(largest);
}
}
void buildMaxHeap(int A[]){
//从后向前 保证交换上来的最大
for(int i=H/2;i>=1;i--)
maxHeapify(i);
}
考察:设二叉堆的大小为H,maxHeapify的复杂度与完全二叉树的高成正比,因此为。然后我们来看buiildMaxHeap的复杂度,设元素数为H,程序首先要对高度为1的H/2个子树执行maxHeapify,然后对高度为2的H/2子树执行maxHeapify,...,最后堆高度为H的一个子树执行maxHeapify,因此复杂度为。
优先级队列(priority queue)是一种数据结构,其存储的数据集合S中,各个元素均包含键值。只要包含如下 如下操作:
- insert(S,k):向集合S中插入元素k
- extractMax(S):从S中删除键值最大的元素并返回该键值
优先取出最大键值的队列称为最大优先队列,可以通过最大堆实现。
向队列中插入元素并更改优先队列中的值,如若插入元素的键值小于当前键值不做改变。否则,则和父亲结点的键值比较,交换。
#define INFTY (1<<30)
void insert(int key){
H++;
A[H]=-INFTY;
//在H位置插入key
increaseKey(H,key);
}
void increaseKey(int i,int key){
if(key<A[i])
return ;
A[i]=key;
while(i>1&&A[i/2]<A[i]){
swap(A[i/2],A[i]);
i=i/2;
}
}
接下来的就是删除操作,在删除过后需要更新最大值,那么我们将A[H]->A[1],然后进行一次maxHeapify()把其值向下传递,最大值自然就上来了(原来的A[1]的左右子结点的值被替换上来成为最大)。
void maxHeapify(int i){
int l=i*2;
int r=i*2+1;
int largest;
if(l<=H&&A[i]<A[l])
largest=l;
else
largest=i;
if(r<=H&&A[largest]<A[r])
largest=r;
if(largest!=i){
swap(A[largest],A[i]);
maxHeapify(largest);
}
}
int extract(int key){
int maxv;
if(H<1)
return -INTFY;
maxv=A[1];
A[1]=A[H--];
maxHeapify(1,A[1]);
return maxv;
}
主函数在这里:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAX 2000000
#define INFTY (1<<30)
int H,A[MAX+1];
int main(){
int key;
char com[10];
while(1){
scanf("%s",com);
if(com[0]=='e'&&com[1]=='n')
break;
if(com[0]=='i'){
scanf("%d",&key);
insert(key);
}else{
printf("%d\n",extract());
}
//for(int i=1;i<=H;i++)
//printf("%d ",A[i]);
}
return 0;
}
优先队列的插入、删除操作的复杂度均为,遍历或每一个元素罢了,与树高(H)成正比。
STL——priority_queue执行模式的接口与queue相同
- push()用于向队列中插入一个元素
- top()返回开头元素的值
- pop()删除开头元素
- ...
定义一个优先队列的简洁版格式:
#include<queue>
using namespace std;
priority_queue<int> PQ;
//递减排列
priority_queue<int,vector<int>,less<int> >PQ1;//greater