用Python實現數據結構之優先級隊列
優先級隊列
如果我們給每個元素都分配一個數字來標記其優先級,不妨設較小的數字具有較高的優先級,這樣我們就可以在一個集合中訪問優先級最高的元素並對其進行查找和刪除操作了。這樣,我們就引入了優先級隊列 這種數據結構
最簡單的優先級隊列可能就是一堆不同大小的數組成的隊列,每次需要取出其中最小或最大的數,這是我們可以把這些數本身的大小叫做他們的優先級。
實現的想法
最簡單的想法是:我們用一個元組來表示元素和它的優先級,將所有的元組都放到列表中存儲,接下來當想要找到其中優先級最小的元組時會有以下兩種方式
-
1.列表中存儲的是亂序的,每次想找最小的時候就遍歷一遍找到優先級最小的元組取出。這樣操作的時間復雜度其實是O(n)
-
2.列表中的元素已經按照優先級的順序排好了序,每次取最小的元素時直接找固定位置,但是每次向該優先級隊列插入元素時都要進行一次排序將其放入合適的位置,在最壞情況下,時間復雜度同樣為O(n)
上面這兩種方式的時間復雜度還是較高的,為此,我們使用一種叫做堆的結構來實現優先級隊列。
堆
堆其實是一顆二叉樹,但它是一種特殊的二叉樹,堆中的每個父節點都是要大於等於或者小於等於它的孩子節點。這裏是選擇大於等於還是小於等於是自己定義,但樹中的所以節點都滿足一條同樣的規則。
在使用堆時,一般我們會想讓堆的高度盡可能的小,為此它必須是一顆完全二叉樹,這時就會產生一個結論:
堆中有n個元素,那麽它的高度h=[log(n)]
我們證明一下這個結論:
完全二叉樹0~h-1層的節點數目為2^h-1,在第h層,節點數目最少為1,最多為2^h
於是,n>=2^h-1+1且n<=2^h-1+2^h
分別兩邊取對數,得出h<=log(n)和h>=log(n+1)-1
由於h是整數,所以h=[log(n)]
用堆實現優先級隊列
插入元素
插入元素包括向堆中添加一個元素和堆向上冒泡
添加元素時要為了滿足 完全二叉樹的特性,需要將其放到樹最下層的最右節點的最右位置,如果最下層已經滿了,則放到再下一層的最左位置。
堆向上冒泡是一個很有趣的算法,為了使添加元素後的樹滿足堆排序,需要做一定的調整,調整方法為將添加的元素的優先級與其父節點相比較,如果小於父節點,則該元素與父節點交換,然後再與新的父節點比較,知道父節點小於了自己的優先級或者自己成為了根節點。如圖:
上面的樹調整了之後變成了下面的樹
移除最小元素
移除最小元素,按理說最小元素就是二叉樹的根節點,但是將根節點刪除之後,就變成了兩顆分離的樹,為了保持二叉樹的完整性,我們要進行如下操作
首先將根節點與最下層的最右端的節點交換,然後刪除這最下層最右端的節點,然後再進行堆的向下排序
堆的向下排序即為將根節點與兩個孩子中最小的比較,如果該節點比孩子節點大,則與孩子節點交換,然後繼續向下進行直到該節點比兩個孩子節點都小或者該節點已經沒有孩子了為止。如圖:
堆的插入與移除元素的復雜度都是log(n)
python實現
對於二叉樹的實現,這次我們不使用鏈式結構,因為堆的排序中,需要定位最下層最右端的這個節點,鏈式實現起來較為復雜,同時堆是完全二叉樹,所以使用基於列表的方式會使問題方便很多
先介紹一下這種實現方式:
列表的首個元素即為二叉樹的根節點,所以根節點的索引為1
設節點p的索引函數為f(p)
如果p是位置q的左孩子,則f(p) = 2f(q)+1
如果p是位置q的右孩子,則f(p) = 2f(q)+2
列表中的最後一個元素就是二叉樹的最下層的最右端的元素
下面是具體代碼:
class Empty(Exception):
pass
class HeapPriorityQueue():
"""
使用堆與列表實現的優先級隊列
"""
class Item():
"""
隊列中的項類
"""
def __init__(self, key, value):
self.key = key
self.value = value
def __it__(self, other):
return self.key < other.key
def is_empty(self):
return len(self) == 0
def parent(self, j):
"""
返回父節點的索引
"""
return (j - 1) // 2
def left(self, j):
"""返回左孩子索引"""
return 2 * j + 1
def right(self, j):
"""返回右孩子索引"""
return 2 * j + 2
def has_left(self, j):
"""通過判斷索引是否出了列表來判斷是否存在"""
return self.left(j) < len(self.data)
def has_right(self, j):
return self.right(j) < len(self.data)
def swap(self, i, j):
self.data[i], self.data[j] = self.data[j], self.data[i]
def upheap(self, j):
"""向上堆排序"""
parent = self.parent(j)
if j > 0 and self.data[j] < self.data[parent]:
self.swap(j, parent)
self.upheap(parent)
def downheap(self, j):
"""向下堆排序"""
if self.has_left(j):
left = self.left(j)
small = left
if self.has_right(j):
right = self.right(j)
if self.data[right] < self.data[left]:
small = right
if self.data[small] < self.data[j]:
self.swap(small, j)
self.downheap(small)
def __init__(self):
self.data = []
def __len__(self):
return len(self.data)
def add(self, key, value):
"""添加一個元素,並進行向上堆排序"""
self.data.append(self.Item(key, value))
self.upheap(len(self.data) - 1)
def min(self):
if self.is_empty():
raise Empty(‘Priority queue is empty‘)
item = self.data[0]
return (item.key, item.value)
def remove_min(self):
if self.is_empty():
raise Empty(‘Priority queue is empty‘)
self.swap(0, len(self.data) - 1)
item = self.data.pop()
self.downheap(0)
return (item.key, item.value)
python的heapq模塊
Python標準包含了heapq模塊,但他並不是一個獨立的數據結構,而是提供了一些函數,這些函數吧列表當做堆進行管理,而且元素的優先級就是列表中的元素本身,除此之外它的模型與實現方式與剛才我們自己定義的基本相同
有以下函數:
-
heappush(L,e): 將元素e存入列表L中並進行堆排序
-
heappop(L): 取出並返回優先級最小的元素,並重新堆排序
-
heappushpop(L,e): 將e放入列表中,同時返回最小元素,相當於先執行push,再pop
-
heap replace(L,e): 與heappushpop類似,是先執行pop,再執行push
-
heapify(L): 將未堆排序的列表進行調整使之滿足堆的結構。采用了自底向上的堆構造算法,時間復雜度為O(n)
-
等等
用Python實現數據結構之優先級隊列