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)

採用線性表實現優先隊列,不管是連續表仍是鏈表,在插入元素與取出元素的操做中總有一種是具體線性複雜度的操做app

 優先隊列的堆實現:

"""基於堆(小頂堆)實現優先隊列
堆中每一個結點的數據均小於或等於其子結點的數據
解決堆插入和刪除的關鍵操做稱爲篩選,分向上篩選,向下篩選
去掉一個堆中最後的元素(最下層的最右結點),剩下的元素仍構成一個堆
- 插入元素:
    在一個堆的最後加入一個元素,獲得的結果還能夠看做徹底二叉樹,但未必是堆,須要作一次向上的篩選
- 向上篩選:
    不斷用新加入的元素(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)的最壞狀況ui

相關文章
相關標籤/搜索