用python实现堆

堆是一种数据结构,它是一棵完全二叉树,且某个节点的值总是不大于或不小于其父节点的值;根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。根据完全二叉树的性质,若将堆中的数据至顶向下,从左向右的存在一个一维数组里面,则父节点的位置索引总是该节点位置索引减1再除2取整的结果,如下图所示。

 若直接将一组数如 [8,5,2,9,3,7,1,4,6] 放入上图所示的二叉树(如下图),此时该二叉树还不能称为堆,因为它并不具有数据的有序性(假如我们要实现的是小根堆,每个节点值都要不大于其子节点)。因此必须要采取操作使得其成为堆。

下面介绍几种堆的基本操作

上浮 shift up

小根堆中越小的元素应该越在上面。以上图中6号位置元素1为例,它比它的父节点2小,则它应该和2交换位置,此时1在2号位置。这时1还比它的父节点8小,则它和8交换位置,1在0号位置了。此时1的位置是合理的。这个过程就叫上浮。总结一下就是:

从当前结点开始,和它的父亲节点比较,若是比父亲节点小,就交换,然后将当前询问的节点下标更新为原父亲节点下标;否则结束。 

下沉 shift_down

下沉的目的是让大元素沉在堆的下面。还以上图的子树为例,0位置的8的子节点5和2都比它小,最小的是2,则2和8交换位置,8沉到2号位置,此时它的子节点7和1也都比它小,最小的是1,那就和1交换位置,8沉到了6号位置,结束。总结出下沉操作过程就是让当前结点的左右儿子(如果有的话)作比较,哪个比较小就和它交换,并更新询问节点的下标为被交换的儿子节点下标,否则结束。

 以上面两种基本操作为基础,就很容易实现下面两种操作。

插入 push

在向堆中插入元素时,我们总是将它放在堆的最后的位置,然后将它上浮,这样就能继续维持堆数据的有序性了。

弹出 pop

弹出操作演出的是堆顶的元素,也就是完全二叉树的根节点。若直接弹出根节点,则原来的一棵完全二叉树就变成两棵完全二叉树,这样对继续维护堆造成困难,此时我们将二叉树中最后位置的元素放到根节点位置,这样又是一棵完全二叉树了,然后将现在的根元素下沉就行。

以上是堆的一些操作的基本原理,但在python中实现堆时,操作过程略有不同。为了节省内存,在执行上浮操作时,不是逐次交换位置,而是拿着要上浮的元素去比较,找到合适的位置。下沉操作也是一样。

以下是在python中实现的Heap类。

class Heap:
    def __init__(self,elist):
        self._elems=list(elist)
        if elist:
            self.buildheap()

    def is_empty(self):
        return not self._elems

    #取堆顶元素
    def peek(self):
        if self.is_empty():
            raise ValueError("堆为空")
        return self._elems[0]

    #上浮
    def siftup(self,e,last):
        elems,i,j=self._elems,last,(last-1)//2
        while i>0 and e<elems[j]:
            elems[i]=elems[j]
            i,j=j,(j-1)//2
        elems[i]=e

    #插入
    def push(self,e):
        self._elems.append(None)
        self.siftup(e,len(self._elems)-1)

    #下沉
    def siftdown(self,e,begin,end):
        elems,i,j=self._elems,begin,begin*2+1
        while j<end:
            if j+1<end and elems[j+1]<elems[j]:
                j+=1
            if e<elems[j]:
                break
            elems[i]=elems[j]
            i=j
            j=2*j+1
        elems[i]=e

    #弹出
    def pop(self):
        if self.is_empty():
            raise ValueError("堆为空")
        elems=self._elems
        e0=elems[0]
        e=elems.pop()
        if len(elems)>0:
            self.siftdown(e,0,len(elems))
        return e0

    #从数组构建堆
    def buildheap(self):
        end=len(self._elems)
        for i in range(end//2-1,-1,-1):
            self.siftdown(self._elems[i],i,end)

在堆的初始化函数中,传入的是一个list,函数中使用的是它的拷贝,这是避免直接引用该list对象会造成的麻烦。然后直接从list创建一个堆,采用的是逐个元素下沉的方式,并不断扩大下沉范围的上限i。注意i是从end//2-1开始,这是因为只有从下标end//2-1开始向上位置的元素才有子节点。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值