"""基於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