heapq-堆排序算法
heapq實現了一個適合與Python的列表一起使用的最小堆排序算法。
二叉樹
樹中每個節點至多有兩個子節點
滿二叉樹
樹中除了葉子節點,每個節點都有兩個子節點
什么是完全二叉樹
在滿足滿二叉樹的性質后,最后一層的葉子節點均需在最左邊
什么是堆?
堆是一種數據結構,它是一顆完全二叉樹。最小堆則是在堆的基礎增加了新的規則,它的根結點的值是最小的,而且它的任意結點的父結點的值都小於或者等於其左右結點的值。因為二進制堆可以使用有組織的列表或數組來表示,所以元素N的子元素位於位置2 * N + 1和2 * N + 2。這種布局使重新安排堆成為可能,因此在添加或刪除項時不需要重新分配那么多內存
區分堆(heap)與棧(stack):堆與二叉樹有關,像一堆金字塔型泥沙;而棧像一個直立垃圾桶,一列下來。
最大堆
最大堆確保父堆大於或等於它的兩個子堆。
最小堆
最小堆要求父堆小於或等於其子堆。Python的heapq模塊實現了一個最小堆。
創建一個堆
示例代碼:
heapq_heapdata.py
# This data was generated with the random module.
data = [19, 9, 4, 10, 11]
堆輸出使用heapq showtree.py打印。
heapq_showtree.py
import math
from io import StringIO
def show_tree(tree, total_width=36, fill=' '):
"""Pretty-print a tree."""
output = StringIO()
last_row = -1
for i, n in enumerate(tree):
if i:
row = int(math.floor(math.log(i + 1, 2)))
else:
row = 0
if row != last_row:
output.write('\n')
columns = 2 ** row
col_width = int(math.floor(total_width / columns))
output.write(str(n).center(col_width, fill))
last_row = row
print(output.getvalue())
print('-' * total_width)
print()
這里有兩種方案創建一個堆,一種是使用heappush(),一種是使用heapify()。
heappush
heapq_heappush.py
import heapq
from heapq_showtree import show_tree
from heapq_heapdata import data
heap = []
print('random :', data)
print()
for n in data:
print('add {:>3}:'.format(n))
heapq.heappush(heap, n)
show_tree(heap)
使用heappush()時,當從數據源添加新項時,將維護元素的堆排序順序。
python3 heapq_heappush.py
random : [19, 9, 4, 10, 11]
add 19:
19
------------------------------------
add 9:
9
19
------------------------------------
add 4:
4
19 9
------------------------------------
add 10:
4
10 9
19
------------------------------------
add 11:
4
10 9
19 11
------------------------------------
如果數據已經在內存中,那么使用heapify()重新排列列表中的項會更有效。
heapify
heapq_heapify.py
import heapq
from heapq_showtree import show_tree
from heapq_heapdata import data
print('random :', data)
heapq.heapify(data)
print('heapified :')
show_tree(data)
按照堆順序每次構建一項列表的結果與構建無序列表然后調用heapify()相同。
$ python3 heapq_heapify.py
random : [19, 9, 4, 10, 11]
heapified :
4
9 19
10 11
------------------------------------
訪問堆的內容
使用heappop()彈出並返回堆中的最小項,保持堆不變。如果堆是空的,則引發IndexError。
heapq_heappop.py
import heapq
from heapq_showtree import show_tree
from heapq_heapdata import data
print('random :', data)
heapq.heapify(data)
print('heapified :')
show_tree(data)
print()
for i in range(2):
smallest = heapq.heappop(data)
print('pop {:>3}:'.format(smallest))
show_tree(data)
在本例中,使用heapify()和heappop()用於對數字列表進行排序。
$ python3 heapq_heappop.py
random : [19, 9, 4, 10, 11]
heapified :
4
9 19
10 11
------------------------------------
pop 4:
9
10 19
11
------------------------------------
pop 9:
10
11 19
------------------------------------
要刪除現有元素並用單個操作中的新值替換它們,請使用heapreplace()。
heapreplace
heapq_heapreplace.py
import heapq
from heapq_showtree import show_tree
from heapq_heapdata import data
heapq.heapify(data)
print('start:')
show_tree(data)
for n in [0, 13]:
smallest = heapq.heapreplace(data, n)
print('replace {:>2} with {:>2}:'.format(smallest, n))
show_tree(data)
替換適當的元素可以維護固定大小的堆,比如按優先級排序的作業隊列。
$ python3 heapq_heapreplace.py
start:
4
9 19
10 11
------------------------------------
replace 4 with 0:
0
9 19
10 11
------------------------------------
replace 0 with 13:
9
10 19
13 11
------------------------------------
堆中的數據極端值
heapq還包含兩個函數,用於檢查一個迭代器,並找到它所包含的最大或最小值的范圍。
heapq_extremes.py
import heapq
from heapq_heapdata import data
print('all :', data)
print('3 largest :', heapq.nlargest(3, data))
print('from sort :', list(reversed(sorted(data)[-3:])))
print('3 smallest:', heapq.nsmallest(3, data))
print('from sort :', sorted(data)[:3])
使用nlargest()和nsmallest()僅對n> 1的相對較小的值有效,但在少數情況下仍然可以派上用場。
$ python3 heapq_extremes.py
all : [19, 9, 4, 10, 11]
3 largest : [19, 11, 10]
from sort : [19, 11, 10]
3 smallest: [4, 9, 10]
from sort : [4, 9, 10]
有效地合並排序Sequences
對於小數據集來說,將幾個排序的序列組合成一個新的序列是很容易的。
list(sorted(itertools.chain(*data)))
對於較大的數據集,這種技術可以使用相當大的內存。merge()不是對整個組合序列進行排序,而是使用堆每次生成一個新序列中的一個項,並使用固定數量的內存確定下一個項。
heapq_merge.py
import heapq
import random
random.seed(2016)
data = []
for i in range(4):
new_data = list(random.sample(range(1, 101), 5))
new_data.sort()
data.append(new_data)
for i, d in enumerate(data):
print('{}: {}'.format(i, d))
print('\nMerged:')
for i in heapq.merge(*data):
print(i, end=' ')
print()
因為merge()的實現使用堆,所以它根據要合並的序列的數量而不是這些序列中的項的數量來消耗內存。
$ python3 heapq_merge.py
0: [33, 58, 71, 88, 95]
1: [10, 11, 17, 38, 91]
2: [13, 18, 39, 61, 63]
3: [20, 27, 31, 42, 45]
Merged:
10 11 13 17 18 20 27 31 33 38 39 42 45 58 61 63 71 88 91 95
上面是小根堆的相關操作。python的heapq不支持大根堆,在stackoverflow上看到了一個巧妙的實現:我們還是用小根堆來進行邏輯操作,在做push的時候,我們把最大數的相反數存進去,那么它的相反數就是最小數,仍然是堆頂元素,在訪問堆頂的時候,再對它取反,就獲取到了最大數。思路很是巧妙。下面是實現代碼
class BigHeap:
def init(self):
self.arr = list()
def heap_insert(self, val):
heapq.heappush(self.arr, -val)
def heapify(self):
heapq.heapify(self.arr)
def heap_pop(self):
return -heapq.heappop(self.arr)
def get_top(self):
if not self.arr:
return
return -self.arr[0]