基本数据结构:二叉堆(堆排序)

导入

我们在解决问题时经常会遇到需要不断维护一段数据内最值的情况,用更精确的话来说就是:存在集合S,里面的元素可能会随时增加、删除、修改,而我们需要做到随时返回集合内的最值,如果采用遍历的方式维护最值,则每次维护都将遍历所有数据,其效率往往十分低下。为解决这个问题,我们就引入了一个新的数据结构——二叉堆。

二叉堆

二叉堆实际上是一种特殊的二叉树,它在普通的二叉树基础上添加了以下条件:

1.二叉堆是一颗完全二叉树

2.二叉堆定义了一个优先级,其父结点的优先级一定大于左右子节点的优先级

注意:有另外一种数据结构与二叉堆相似,但是本质完全不同——二叉搜索树,不要混淆它们的定义。

如果我们规定数据越小优先级越高,那么对于数据:1 3 4 6 8 10 ,我们可以得到如下图所示的二叉堆结构:                         

                                                                      

那么,上述的堆结构是如何建立出来的呢?这就涉及到了二叉堆的基本操作,我们一一来看:

插入操作:插入操作必须遵循二叉堆的定义,即父结点优先级高于子结点优先级。在实际操作中,我们采取尾部添加的策略:插入时,先将数据添加到堆的末尾:如果是左子结点,则直接与父结点比较;如果是右子结点,则先与左子结点比较,其中优先级高的再与父结点比较。再依据优先级规则将其向根节点移动,直到符合二叉堆定义为止。

例如,我们要在上述二叉堆中插入数据2,则首先将其添加到尾部,发现其是右子结点,且优先级高于左子结点,则直接与父结点比较,发现不满足父结点优先级高于子结点,则将它们交换位置,此时满足二叉堆定义,插入成功:

                            

由于树高最多logn层,所以单次插入操作的时间复杂度为o(logn)。

二叉堆的建立

二叉堆的建立有两种方式,第一就是直接对所有数据进行插入操作,这样对n个数据的时间复杂度为O(nlogn)。

但同时我们有更高效的建立方式:将所有数据按树形结构存储,然后从最后一颗非叶子子树开始,将其根结点下沉,单独维护一个堆序,在所有子树都维护完成时,最后维护它本身。

换一句话描述就是:对给定的树形结构,将其所有的非叶子结点进行下沉操作,即可得到堆结构。

                           

      

该过程的时间复杂度是可以计算得到的,对于n个数据,按最坏情况考虑(即构成满二叉树),我们需要处理的所有非叶子结点个数为,对于每个结点的下沉操作,所花费的步骤最多不超过其离树的底层的高度:,所以总共的时间复杂度为,该式子用分配律稍作变形,可以得到两个求和式相减,而前一个求和式是一个简单的等比数列求和,后一个求和式则可以使用错位相减法计算,具体步骤这里就省略了,感兴趣的读者可以自行计算,最终能得到总的时间复杂度为O(n)。

删除操作:对于二叉堆的删除,我们只允许其删除其优先级最高的元素,即二叉堆的根结点,删除后整个堆的形态需要重新确定,这里仍旧采用尾部处理的方法:删除根结点后,将堆的末尾的数据移动到根结点,然后依据优先级规则将其向堆底移动,即总是下沉到左右子结点中更小的一边,直到符合二叉堆定义为止。

示例代码:(以下代码通过了几个简单例题(数据10000以内)的测试,但不保证没有bug存在)

//这里的堆代码中,数组要从1开始。
int heap[10010],size=0;
void swap(int &x,int &y){
	int tmp=x;
	x=y, y=tmp;
}
//上浮操作
void rise(int k){
	if(k==1) return;//到达根结点时不再上浮
	int father=k>>1;
	if(heap[father] <= heap[k]) return;//若满足父结点优先级大于子结点,则停止上浮 
	swap(heap[father],heap[k]);//若不满足,则交换父子结点的位置
	rise(father);//对父结点继续进行上浮操作
}
//下沉操作
void sink(int k){
	int l = k<<1, r = l+1;
	if(l>size) return; //到达叶子结点时不再下沉
	int t;
	if(r>size) t=l;//若当前要下沉的结点没有右子结点,则直接与左子结点比较
	else t=heap[l]<heap[r]? l : r;//若当前要下沉的结点同时有左右结点,则与左右子结点中优先级更高的比较
	if(heap[k] <= heap[t]) return;//若满足父结点优先级大于子结点,则停止下沉 
	swap(heap[k],heap[t]);//若不满足,则交换父子结点的位置
	sink(t);//对子结点继续进行下沉操作
}
//添加元素
void push(int x){
	heap[++size]=x;//尾部插入
	rise(size);    //执行上浮操作
}
//删除优先级最高的元素
void pop(){
	heap[1]=heap[size--];//用尾部数据替换根结点
	sink(1);             //执行下沉操作
}
//高效建立一个二叉堆
void build(int n){
	int k=n/2;           //找到最后一个非叶子子树的根节点
	size=n;              //确定堆的大小
	while(k) sink(k),k--;//依次向前进行下沉操作
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值