优先队列

优先队列也是一种重要的缓存结构,基于二叉树,可以做出优先队列的一种高效实现。

概念

优先队列与栈和队列类似,将数据元素保存在其中,可以访问和弹出。优先队列的特点是存入其中的元素都另外附有一个值,表示这个元素的优先程度,称其为优先值。优先队列应该保证在任何时候,弹出的应该是保存在这个容器中优先值最高的元素,如果该元素不弹出,再次访问时仍弹出该元素。
也有可能出现这种情况:优先队列中有两个元素有一样的优先程度,这时就要考虑弹出的方法:一是相同优先程度的元素先进先出,那么就会做出效率比较低的实现;另一种是只要弹出的是最高优先程度的就可以,不保证哪个先出,这样可以做出效率较高的实现。
优先队列的操作:
- 创建,判断空,还可以有清空内容、确定当前元素个数等。
- 插入元素,访问和弹出当前当前优先程度最高的元素。


基于线性表的实现

数据项在连续表里的存储顺序可用于表示数据之间的某种顺序关系,对于优先队列,这个顺序可用于表示优先级关系,让数据的存储顺序按优先顺序排列。
这里有两种实现方案:
1、在存入数据时,保证表中元素始终按优先顺序排列,任何时候都可以直接访问当时表里优先最高的元素;采用有组织的存入方式,存入元素的操作效率可能比较低,但访问和弹出时比较方便。
2、存入数据时用最简单的方法,顺序表插入尾端,链接表插入首端;需要取用时,通过检索找到优先程度最高的的元素。采用这种方式存入效率高,但取用效率低。

基于list实现优先队列

假定需要存储的数据用“<=”比较优先级,值较小的元素优先级更高。
首先定义一个异常类:

class PrioQueueError(ValueError):
    pass

将优先队列定义为一个类:

class PrioQue:
    #这里用list转换,首先对实参做一个拷贝,以免共享;另外,可以使构造函数实参可以是任何可迭代对象。
    def __init__(self, elist=[]):
        self._elems = list(elist)
        self._elems.sort(reverse=True)
#从最右边开始与e比较,当遇到比e大的元素时就插入到他的后边,同时也保证了相同优先级元素先进先出
    def enqueue(self, e):
        i = len(self.elems) - 1
        while i >= 0:
            if self._elems[i] <= e:
                i -= 1
            else:
                break
        self._elems.insert(i+1, e)

    def is_empty(self):
        return not self._elems

    def peek(self):
        if self.is_empty():
            raise PrioQueueError('in top.')
        return self._elems[-1]

    def dequeue(self):
        if self.is_empty():
            raise PrioQueueError('in pop.')
        return self._elems.pop()

采用这种方式实现的优先队列,插入元素是 O(n) O ( n ) 操作,其他都是 O(1) O ( 1 ) 操作。

树形结构和堆

由于线性表的特点,只要元素按优先级顺序排列,就无法避免线性复杂性问题,如果不改变数据的线性顺序存储方式,就不可能突破 O(n) O ( n ) 的复杂度限制。要做出效率更高的优先队列就必须考虑其他的数据结构组织方式。

堆及其性质

从结构上看,堆就是结点里存储数据的完全二叉树,但堆中存储数据需要满足一种特殊的堆序:任一个结点里所存的数据,先于或等于其子结点里的数据。
- 在一个堆中从树根到任何一个叶结点的路径上,各结点里所存的数据按规定的优先关系递减。
- 堆中最优先的元素必定位于二叉树的跟结点里, O(1) O ( 1 ) 时间就能得到。
- 位于树中不同路径上的元素,这里不关心其顺序关系。
根据所要求的序,分为小顶堆和大顶堆。
一棵完全二叉树可以自然的存入一个连续线性结构,因此,一个堆也可以存入一个连续表,通过下标就可以方便的找到它的父/子结点。
堆和完全二叉树还有下面几个性质:
1、在一个堆的最后加入一个元素,整个结构还可以看成是完全二叉树,但不一定是堆。
2、一个堆去掉堆顶,其余元素形成两个子堆。
3、给由2得到的表加入一个根元素,得到的又可以看作完全二叉树,但不一定是堆。
4、去掉一个堆的最后元素,剩下的仍然是一个堆。

优先队列的堆实现

这里需要考虑两个问题:
1、如何实现插入元素的操作,向堆中插入一个元素,结果还为堆。
2、如何实现弹出元素的操作,弹出元素后,剩余元素重新做成堆。
解决堆插入和弹出的关键操作称为筛选,又分为向上筛选和向下筛选。


首先考虑堆中加入元素的操作,为了在堆的最后加入一个元素仍然回复称堆,就需要做一次向上筛选:不断用新加入的元素 e e 与其父结点比较,如果e较小就交换两个元素的位置,通过这样的比较和交换,元素 e e 不断上移,直到到达特定位置才会停下,这样就得到了基于堆的优先队列插入操作:
- 把新加入元素放在已有元素之后,执行一次向上筛选。
- 向上筛选操作不会超过当前二叉树最长路径的长度,综合操作时间为O(logn)


由于堆顶元素就是最优先元素,应该弹出的元素就是它,但弹出后剩下的元素不再是堆,依据上边第四条,可以取出最后一个元素,把这个元素放在堆顶,就得到了一棵完全二叉树,但是由于根的元素不满足,所以不是堆,下面需要设法把结构重新恢复成堆:
这种情况下的操作称为向下筛选:设两个子堆A、B加上元素e构成一棵完全二叉树,把它们做成一个堆,操作步骤为:
1、用e与A、B两个子堆的堆顶比较,最小者作为整个堆的堆顶。
- 如果e不是最小,则为A或B,将最小的一个移到堆顶,相当于删去了一个子堆的堆顶。
- 下面把e放入到删去堆顶的子堆,然后同上方一样,继续循环。
2、如果某次比较中e最小,则整个结构就成为堆。
3、如果e已经落到低,整个结构也成为堆。
弹出操作和取最后一个元素操作都为 O(1) O ( 1 ) 时间,向下筛选操作为 O(logn) O ( l o g n ) ,综合起来也是 O(logn) O ( l o g n ) 时间操作。

基于堆的优先队列类
class PrioQueue:
    def __init__(self, elisy=[]):
        self._elems = list(elist)
        if elist:
            self.buildheap() 

    def is_empty(self):
        return not self._elems

    def peek(self):
        if self.is_empty():
            raise PrioQueueError("in peek.")
        return self._elems[0]

    def enqueue(self, e):
        self._elems.qppend(None)
        self.siftup(e, len(self._elems)-1)

#先插入一个空值,每次用该位置的父元素与e比较,知道找到合适的位置,再把e插入该位置。
    def siftuo(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 dequeue(self):
        if self.is_empty():
            raise PrioQueueError("in dequeue.")
        elems = self._elems
        e0 = elems[0]
        e = elems.pop()
        if len(elems) > 0:
            self.siftdown(e, 0, len(elems))
        return e0
#
    def siftdown(self, e, begin, end):
        elems, i, j = self._elems, begin, begin*2+1
        while j < end:   #j不能超出堆外
            if j+1 < end and elems[j+1] < elems[j]:  #下一层的两个兄弟结点比较
                j += 1
            if e < elems[j]:   #如果e比下一层两个兄弟结点较小的那一个还小,则不用再筛选
                break
            elems[i] = elems[j]   #如果e比较大,则交换位置
            i, j = j, 2*j+1
        elems[i] = e

最后考虑堆的初始构建,一个元素的序列是一个堆,从下标 end//2 e n d / / 2 的位置开始,后面的元素都为叶结点,都是一个个单独的堆,从这里开始往前,向左一个个建堆,直到整个表建成一个堆。

    def buildheap(self):
        end = len(self._elems)
        for i in range(end//2, -1, -1):   #从最后一个分支节点开始,把他和他的子结点看成需要向下筛选的一个整体,依次往前,直到每个分支节点都向下筛选一次
            self.siftdown(self._elems[i], i, end)
堆的应用:堆排序

如果一个连续表里存储的数据是一个小顶堆,按优先队列的操作方式反复弹出堆顶元素,能够udedao一个递增序列,这样可以实现一种连续表中元素的排序工作。

def heap_sort(elems):
    def siftdown(elems, e, begin, end):
        i, j = 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

    end = len(elems)
    for i in range(end//2, -1, -1):  #第一次循环把列表变成堆
        siftdown(elems, elems[i], i, end)
    for i in range((end-1), 0, -1):  #第二次循环
        e = elems[i]                 #把最后一个元素取出,把第一个元素放在最后,就变成了以第i个元素为跟,和前边两个子堆向下筛选构成新堆的问题
        elems[i] = elems[0]
        siftdown(elems, e, 0, i)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值