数据结构与算法——26. 优先队列与二叉堆

一、优先队列(Priority Queue)

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

例如:银行窗口取号排队,VIP客户可以插到队首;操作系统中执行关键任务的进程或用户特别指定进程在调度队列中靠前。

实现优先队列的经典方案是采用二叉堆数据结构,二叉堆能够将优先队列的入队和出队复杂度都保持在 O ( log ⁡ n ) O(\log n) O(logn)

二、二叉堆(Binary Heap)

二叉堆的有趣之处在于,其逻辑结构上像二叉树,却是用非嵌套的列表来实现的!其中,最小数据项排在队首的称为“最小堆min heap”;反之,最大数据项排在队首的是“最大堆max heap”。

为了使堆操作的时间复杂度能保持在对数水平上, 所以必须采用二叉树结构;同样, 如果要使操作始终保持在对数数量级上, 就必须始终保持二叉树的“平衡”(这种二叉树也叫“平衡二叉树”:树根左右子树拥有相同数量的节点)。所以,我们采用“完全二叉树”的结构来近似实现“平衡”。

1. 完全二叉树

完全二叉树的叶节点最多只出现在最下面的两层,而且最底层的叶节点都连续集中在左边。

在这里插入图片描述

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

在这里插入图片描述

比如,上图中的完全二叉树(也可以看作二叉堆),数据项为9的节点,其下表为2,那么,他的左子节点下标为2*2=4,右子节点下标为2*2+1=5。

2. 堆次序(Heap Order)

任何一个节点x,其父节点中的值均小于x中的值。这样,符合“堆”性质的二叉树,其中任何一条从上到下的路径,均是一个已从小到大排序的数列,根节点的值最小

三、二叉堆的python实现

__init__初始化方法:我们采用一个列表来保存堆数据,其中表首下标为0的项无用(因为下标是从1开始的),但为了后面代码可以用到简单的整数乘除法,仍保留它。

class Binheap:
    def __init__(self):
        self.heapList = [0]
        self.currentSize = 0

insert插入方法:首先,为了保持“完全二叉树”的性质,新插入到列表末尾的数据项,需要将新数据项沿着路径“==上浮”==到正确的位置。(注意:新数据项的“上浮”不会影响其它路径节点的“堆”次序)

在这里插入图片描述

def percUp(self, i):
    while i // 2 > 0:  # 当父节点下标大于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  # 堆的大小加1
    self.percUp(self.currentSize)  # 上浮,比较大小

delMin方法,移除整个堆中最小的数据项:根节点heapList[1]。

为了保持“完全二叉树”的性质,只用最后一个节点来代替根节点。然后将新的根节点“下沉”,直到比两个子节点都小。“下沉”路径的选择:如果比子节点大,那么选择较小的子节点交换下沉

def percDown(self, i):
    while (i * 2) <= self.currentSize:
        mc = self.minChild(i)  # 找出较小的子节点
        if self.heapList[i] > 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.hepList[i * 2 + 1]:
            return i * 2
        else:
            return i * 2 + 1


def delMin(self):
    retval = self.heapList[1]  # 移走堆顶
    self.heapList[1] = self.heapList[self.currenSize]
    self.currentSize -= 1
    self.heapList.pop()
    self.percDown(1)  # 新顶下沉
    return retval

buildHeap方法,从无序表生成“堆”。

我们最自然的想法是:用insert(key)方法,将无序表中的数据项逐个insert到堆中,但这么做的总代价是 O ( n log ⁡ n ) O(n\log n) O(nlogn)。其实,用“下沉”法,能够将总代价控制在 O ( n ) O(n) O(n)

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 -= 1
    print(self.heapList, i)
le i > 0:
        print(self.heapList, i)
        self.percDown(i)
        i -= 1
    print(self.heapList, i)
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

花_城

你的鼓励就是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值