优先队列和二叉堆

1.优先队列(Priority Queue)

前面我们学习了一种FIFO数据结构队列。队列有一种变体称为“优先队列”。如银行窗口取号排队,VIP客户可以插到队首。

  • 优先队列的出队跟队列一样从队首出队
  • 在优先队列内部,数据项的次序是由“优先级”来确定
    高优先级的数据项排在队首,而低优先级的数据项则排在后面。这样,优先队列的入队操作就比较复杂,需要将数据项根据其优先级尽量挤到队列前方。

2. 二叉堆(Binary Heap)实现优先队列

实现优先队列的经典方案是采用二叉堆数据结构。二叉堆能够将优先队列的入队和出队的复杂度都保持在O(logn)
二叉堆的有趣之处在于,其逻辑结构上像二叉树,却是用非嵌套的列表来实现的。最小key排在队首的称为“最小堆min heap”,反之,最大key排在队首的是“最大堆max heap”

ADT BinaryHeap的操作定义如下:

  • BinaryHeap():创建一个空二叉堆对象
  • insert(k):将新key加入到堆中
  • findMin():返回堆中的最小项,最小项仍保留在堆中
  • delMin():返回堆中的最小项,同时从堆中删除
  • isEmpty():返回堆是否为空
  • buildHeap(list):从一个key列表创建新堆

3.用非嵌套列表实现二叉堆

为了使堆操作能保持在对数水平上,就必须采用二叉树结构;同样,如果要使操作始终保持在对数数量级上,就必须始终保持二叉树的“平衡”即树根左右子树拥有相同数量的节点
我们采用“完全二叉树”的结构来近似实现“平衡”
完全二叉树,叶节点最多只出现在最底层和次底层,而且最底层的叶节点都连续集中在最左边,每个内部节点都有两个子节点,最多可有1个节点例外。
在这里插入图片描述

3.1 完全二叉树的列表实现及性质

完全二叉树由于其特殊性,可以用非嵌套列表以简单的方式实现,具有很好的性质。如果节点的下标为p,那么其左子节点下标为2p,右子节点为2p+1,其父节点下标为p//2
在这里插入图片描述

3.2堆次序(Heap Order)

任何一个节点x,其父节点p的key均小于x中的key。这样,符合“堆”性质的二叉树,其中任何一条路径都是一个已排数列,根节点的key最小。
在这里插入图片描述

3.3二叉堆的python实现

3.3.1 二叉堆初始化

采用一个列表来保存堆数据,其中表首下标为0的项无用,但为了后面代码可以用到简单的整数除法,仍保留它。

class BinHeap:
    def __init__(self):
        self.heapList=[0]
        self.currentSize=0
3.3.2 insert(key)方法

首先,为了保持“完全二叉树”的性质,新key应该添加到列表末尾。如下图所示,显然无法保持“堆”次序,虽然对其他路径的次序没有影响,但对于其到根节点的路径(红虚线框出的路径)可能破坏次序。需要将新key沿着路径来“上浮”到其正确位置。
注意:新key的“上浮”不会影响其他路径节点的“堆”次序
在这里插入图片描述
在这里插入图片描述
python代码实现

#insert代码
    def percUp(self,i):#上浮
        while i//2>0:
            if self.heapList[i]<self.heapList[i//2]:#当前节点的值小于父节点,进行交换
                tmp=self.heapList[i//2]
                self.heapList[i//2]=self.heapList[i]
                self.heapList[i]=tmp
            i=i//2#沿路径上浮
    def insert(self,k):
        self.heapList.append(k)#添加到末尾
        self.currentSize=self.currentSize+1
        self.percUp(self.currentSize)#新key上浮
    
3.3.3 delMin()方法

移走整个堆中最小的key即根节点heapList[1]。为了保持“完美二叉树”的性质,只用最后一个节点来代替根节点。同样,这么简单的替换还是破坏了“堆”次序
解决方法:将新的根节点沿着一条路径“下沉”,直接比两个字节点都小
“下沉”路径的选择:如果比子节点大,那么选择比较小的子节点交换下沉
在这里插入图片描述
在这里插入图片描述
python代码实现

#delMin()代码
    def percDown(self,i): #下沉操作
        while (i*2)<=self.currentSize:#向下
            mc=self.minChild(i)
            if self.heapList[i*2]>self.heapList[mc]:
                tmp=self.heapList[i]
                self.heapList[i]=self.heapList[mc]
                self.heapList[mc]=tmp
            i=mc
    def minChild(self,i):#更小的子节点
        if i*2+1>self.currentSize:
            return i*2#唯一子节点
        else:
            if self.heapList[i*2]<self.heapList[i*2+1]:#返回较小的
                return i*2
            else:
                return i*2+1
    def delMin(self):
        retval=self.heapList[1]#移走堆顶
        self.heapList[1]=self.heapList[self.currentSize]
        self.currentSize=self.currentSize-1
        #新顶下沉
        self.heapList.pop()
        self.percDown(1)
        return retval
3.3.4 buildHeap(lst)方法:从无序表生成“堆”

我们最自然的想法是:用insert(key)方法将无序表中的数据项逐个insert到堆中,但这么做的总代价是O(nlogn)。其实用“下沉”法能够将总代价控制在O(n)
在这里插入图片描述
python代码实现

#buildHeap(lst)代码
    def buildHeap(self,alist):
        i=len(alist)//2#从最后节点的父节点开始,因为叶节点无需下沉
        self.currentSize=len(alist)
        self.heapList=[0]+alist[:]
        print(len(self.heapList),i)
        while (i>0):
            print(self.heapList,i)
            self.percDown(i)
            i=i-1
        print(self.heapList,i)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值