07_3.优先队列

连续表实现优先队列:

"""基于list实现优先队列"""


class PrioQueueError(ValueError):
    pass


class PrioQueue:
    """
    数据的存储位置按优先顺序排列
    值较小的元素优先级更高
    """

    def __init__(self, elist=[]):
        self._elems = list(elist)  # 默认值是可变对象,用list转换,做一个拷贝,避免共享
        self._elems.sort(reverse=True)

    def enqueue(self, e):
        i = len(self._elems) - 1
        while i >= 0:
            if self._elems[i] <= e:
                i -= 1
            else:
                break
        # while结束,i为-1或第一个大于e的元素的下标
        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()


p = PrioQueue([1, 2, 3, 4])
print(p._elems)
p.enqueue(5)
print(p._elems)

采用线性表实现优先队列,无论是连续表还是链表,在插入元素与取出元素的操作中总有一种是具体线性复杂度的操作

 优先队列的堆实现:

"""基于堆(小顶堆)实现优先队列
堆中每个结点的数据均小于或等于其子结点的数据
解决堆插入和删除的关键操作称为筛选,分向上筛选,向下筛选
去掉一个堆中最后的元素(最下层的最右结点),剩下的元素仍构成一个堆
- 插入元素:
    在一个堆的最后加入一个元素,得到的结果还可以看作完全二叉树,但未必是堆,需要做一次向上的筛选
- 向上筛选:
    不断用新加入的元素(e)与其父结点的数据比较,如果e较小就交换两个元素的位置,通过比较和交换,元素e
不断上移,一直到e的父结点的数据<=e时,或者e已经到达根结点时停止。

插入操作总结:把新加入元素放在(连续表里)已有元素之后,执行一次向上筛选操作。向上筛选操作中比较和交换的
次数不会超过二叉树中最长路径的长度,根据完全二叉树性质,加入元素操作可在O(logn)时间完成
- 弹出元素:
    由于堆顶元素就是最优元素,应该弹出的元素就是它,剩下的团扇可以看作两个子堆,从原堆的最后取下一个元素,
其余的元素仍然是堆,把这个元素放到堆顶就得到了一棵完全二叉数,需要做向下筛选恢复为一个堆
- 向下筛选:
    两个子堆A,B的顶元素与原堆取的最后一个元素e比较大小,最小者作为整个堆的顶
    * 若e不是最小,最小的必为A或B的根,如果A的最小,将其移到堆顶,相当于删除了A的顶元素
    * 如果某次比较中e最小,以它为顶的局部树已经成为堆,整个结构也成为堆
    * 或e已经落到底,这是整个结构也成为堆
"""


class PrioQueueError(ValueError):
    pass


class PrioQueue:
    """使用一个list存储元素,表尾加入元素,以首端为堆顶"""

    def __init__(self, elist=[]):
        self._elems = list(elist)
        if elist:
            self.buildheap()  # 转变为堆

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

    # 向上筛选
    def siftup(self, e, last):
        elems, i, j = self._elems, last, (last - 1) // 2  # (last - 1) // 2 :最后一个元素的根元素位置
        # 小顶堆,找到正确插入位置,检查过程中逐个下移
        while i > 0 and e < elems[j]:
            elems[i] = elems[j]
            i, j = j, (j - 1) // 2
        elems[i] = e

    def is_empty(self):
        return not self._elems

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

    def dequeue(self):
        """弹出元素"""
        if self.is_empty():
            raise PrioQueueError('in pop')
        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  # begin * 2 + 1: 第一个小堆的堆顶
        while j < end:
            if j + 1 < end and elems[j + 1] < elems[j]:  # 找elems[j + 1]与elems[j]中较小的
                j += 1
            if e < elems[j]:  # e在三者中最小,已找到了位置
                break
            elems[i] = elems[j]  # elems[j]在三者中最小,上移
            i, j = j, 2 * j + 1
        elems[i] = e

    def buildheap(self):
        end = len(self._elems)
        for i in range(end // 2, -1, -1):
            self.siftdown(self._elems[i], i, end)


p = PrioQueue([1, 3, 6, 4, 5])
print(p._elems)
print('========')
print(p.dequeue())
print(p.dequeue())
print(p.dequeue())
print(p.dequeue())
print(p.dequeue())

# [1, 3, 6, 4, 5]
# ========
# 1
# 3
# 4
# 5
# 6

 基于堆实现优先队列,创建操作的时间复杂度是o(n),插入和弹出的复杂度是o(log n)插入操作的第一步是在表的最后加入一个元素,可能导致list对象替换元素存储区,因此可能出现O(n)的最坏情况

posted @ 2019-11-06 14:44  fly_bk  阅读(259)  评论(0编辑  收藏  举报