算法和数据结构——堆

预备知识:


完全二叉树

所有结点深度相同,且所有内部结点都有两个子结点的二叉树称为完全二叉树(Complete Binary Tree)。同时,二叉树的结点深度最大差距为1,最下层叶子都集中在该层最左边的若干位置上,这种二叉树也是(近似)完全二叉树。

设结点数为n,那么完全二叉树的树高则为log_2n。利用这性质,我们可以高速地管理数据。


二叉堆

如果一个完全二叉树各节点键值与一数组的各元素具有对应关系,那么这个二叉树就是二叉堆。

二叉堆的大小为H,那么二叉堆元素就存储在A[1...H]中,其根的下标为1。当给定一个点的下标i时,可以通过\left \lfloor i/2 \right \rfloor2\times i2\times 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的复杂度与完全二叉树的高成正比,因此为O(logH)。然后我们来看buiildMaxHeap的复杂度,设元素数为H,程序首先要对高度为1的H/2个子树执行maxHeapify,然后对高度为2的H/2子树执行maxHeapify,...,最后堆高度为H的一个子树执行maxHeapify,因此复杂度为H\times \sum _{k=1}^{logH}\frac{k}{^{^{2}^k}}=O(H)


优先级队列(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;
} 

 优先队列的插入、删除操作的复杂度均为O(n),遍历或每一个元素罢了,与树高(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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值