堆(heap)
堆是一种完全二叉树的变种,是用数组存放,其中第0个元素是堆的根,编号为k的节点的左右节点分别为2k+1和2k+2,调整后的堆应具有以下特点:父节点小于左右子节点(小根堆)
python中堆的方法
我们学习一下python里的heapq的实现,首先heap包含几个方法:
heappush(heap, item)
尾部添加节点
heappop(heap)
删除头节点
heappushpop(heap, item)
添加元素并删除添加元素后的头节点,该方法优于先push后pop的操作
heapify(x)
将一个list变成heap
heapreplace(heap, item)
删除头节点并把item添加进heap中,该方法优于先pop后push操作
由以上5种操作,衍生出3个常用方法:
*merge(iterables)
将n个列表进行merge
nlargest(n, iterable[, key])
取n个最大数
nsmallest(n, iterable[, key])
取n个最小数
python中堆的实现
def heappush(heap, item):
"""Push item onto heap, maintaining the heap invariant."""
heap.append(item)
_siftdown(heap, 0, len(heap)-1)
def heappop(heap):
"""Pop the smallest item off the heap, maintaining the heap invariant."""
lastelt = heap.pop() # raises appropriate IndexError if heap is empty
if heap:
returnitem = heap[0]
heap[0] = lastelt
_siftup(heap, 0)
else:
returnitem = lastelt
return returnitem
push的方法很简单,将元素添加至末尾,并执行siftdown操作。
pop的方法是先拿出一个last,然后和头节点进行替换,并执行siftup操作。
siftdown操作可以将尾部节点向头部节点进行转移,而siftup操作是将头部节点向尾部节点转移。
def heapreplace(heap, item):
"""Pop and return the current smallest value, and add the new item."""
returnitem = heap[0] # raises appropriate IndexError if heap is empty
heap[0] = item
_siftup(heap, 0)
return returnitem
def heappushpop(heap, item):
"""Fast version of a heappush followed by a heappop."""
if heap and cmp_lt(heap[0], item):
item, heap[0] = heap[0], item
_siftup(heap, 0)
return item
def heapify(x):
"""Transform list into a heap, in-place, in O(len(x)) time."""
n = len(x)
for i in reversed(xrange(n//2)):
_siftup(x, i)
replace操作是将头部节点进行替换,然后并执行siftup。
pushpop操作先将要插入的节点和头节点比较,比如比头节点更小,那么无需改动,如果比头节点大,那么头节点一定会被pop,此时我们只需替换头节点并执行sift操作即可。
heapify操作是从最深的中间父节点开始一步一步进行siftup操作,叶子节点无需进行sift,所以这里用的是n//2。
def _siftdown(heap, startpos, pos):
newitem = heap[pos]
# Follow the path to the root, moving parents down until finding a place
# newitem fits.
while pos > startpos:
parentpos = (pos - 1) >> 1
parent = heap[parentpos]
if cmp_lt(newitem, parent):
heap[pos] = parent
pos = parentpos
continue
break
heap[pos] = newitem
siftdown稍微简单一些,每次找到当前节点的父节点,对比当前节点和父节点,如果当前节点小于父节点则swap并更新pos,否则退出函数。
def _siftup(heap, pos):
endpos = len(heap)
startpos = pos
newitem = heap[pos]
# Bubble up the smaller child until hitting a leaf.
childpos = 2*pos + 1 # leftmost child position
while childpos < endpos:
# Set childpos to index of smaller child.
rightpos = childpos + 1
if rightpos < endpos and not cmp_lt(heap[childpos], heap[rightpos]):
childpos = rightpos
# Move the smaller child up.
heap[pos] = heap[childpos]
pos = childpos
childpos = 2*pos + 1
# The leaf at pos is empty now. Put newitem there, and bubble it up
# to its final resting place (by sifting its parents down).
heap[pos] = newitem
_siftdown(heap, startpos, pos)
siftup比较复杂,首先记录要移动的节点,然后获得当前节点的左右子节点,比较左右子节点哪个更小,将小的节点进行上移,更新当前节点为上移的那个子节点,最后将记录节点设置为叶子节点进行上移操作。这样先往下后往上的操作能够保证在siftdown的过程中没有来自兄弟节点的干扰,因为在siftup的时候已经进行了比较,将更小的元素放在了父节点中。