|
| 1 | +class Heap(object): |
| 2 | + ''' |
| 3 | + 索引从0开始的小顶堆 |
| 4 | + 参考: https://github.com/python/cpython/blob/master/Lib/heapq.py |
| 5 | +
|
| 6 | + author: Ben |
| 7 | + ''' |
| 8 | + |
| 9 | + def __init__(self, nums): |
| 10 | + self._heap = nums |
| 11 | + |
| 12 | + def _siftup(self, pos): |
| 13 | + ''' |
| 14 | + 从上向下的堆化 |
| 15 | + 将pos节点的子节点中的最值提升到pos位置 |
| 16 | + ''' |
| 17 | + start = pos |
| 18 | + startval = self._heap[pos] |
| 19 | + n = len(self._heap) |
| 20 | + # 完全二叉树特性 |
| 21 | + child = pos * 2 + 1 |
| 22 | + # 比较叶子节点 |
| 23 | + while child < n: |
| 24 | + right = child + 1 |
| 25 | + # 平衡二叉树的特性, 大的都在右边 |
| 26 | + if right < n and not self._heap[right] > self._heap[child]: |
| 27 | + child = right |
| 28 | + self._heap[pos] = self._heap[child] |
| 29 | + pos = child |
| 30 | + child = pos * 2 + 1 |
| 31 | + self._heap[pos] = startval |
| 32 | + |
| 33 | + # 此时只有pos是不确定的 |
| 34 | + self._siftdown(start, pos) |
| 35 | + |
| 36 | + def _siftdown(self, start, pos): |
| 37 | + ''' |
| 38 | + 最小堆: 大于start的节点, 除pos外已经是最小堆 |
| 39 | + 以pos为叶子节点, start为根节点之间的元素进行排序. 将pos叶子节点交换到正确的排序位置 |
| 40 | + 操作: 从叶子节点开始, 当父节点的值大于子节点时, 父节点的值降低到子节点 |
| 41 | + ''' |
| 42 | + startval = self._heap[pos] |
| 43 | + while pos > start: |
| 44 | + parent = (pos - 1) >> 1 |
| 45 | + parentval = self._heap[parent] |
| 46 | + if parentval > startval: |
| 47 | + self._heap[pos] = parentval |
| 48 | + pos = parent |
| 49 | + continue |
| 50 | + break |
| 51 | + self._heap[pos] = startval |
| 52 | + |
| 53 | + def heapify(self): |
| 54 | + ''' |
| 55 | + 堆化: 从后向前(从下向上)的方式堆化, _siftup中pos节点的子树已经是有序的, |
| 56 | + 这样要排序的节点在慢慢减少 |
| 57 | + 1. 因为n/2+1到n的节点是叶子节点(完全二叉树的特性), 它们没有子节点, |
| 58 | + 所以, 只需要堆化n/2到0的节点, 以对应的父节点为根节点, 将最值向上筛选, |
| 59 | + 然后交换对应的根节点和查找到的最值 |
| 60 | + 2. 因为开始时待排序树的根节点还没有排序, 为了保证根节点的有序, |
| 61 | + 需要将子树中根节点交换到正确顺序 |
| 62 | + ''' |
| 63 | + n = len(self._heap) |
| 64 | + for i in reversed(range(n // 2)): |
| 65 | + self._siftup(i) |
| 66 | + |
| 67 | + def heappop(self): |
| 68 | + ''' |
| 69 | + 弹出堆首的最值 O(logn) |
| 70 | + ''' |
| 71 | + tail = self._heap.pop() |
| 72 | + # 为避免破环完全二叉树特性, 将堆尾元素填充到堆首 |
| 73 | + # 此时, 只有堆首是未排序的, 只需要一次从上向下的堆化 |
| 74 | + if self._heap: |
| 75 | + peak = self._heap[0] |
| 76 | + self._heap[0] = tail |
| 77 | + self._siftup(0) |
| 78 | + return peak |
| 79 | + return tail |
| 80 | + |
| 81 | + def heappush(self, val): |
| 82 | + ''' |
| 83 | + 添加元素到堆尾 O(logn) |
| 84 | + ''' |
| 85 | + n = len(self._heap) |
| 86 | + self._heap.append(val) |
| 87 | + # 此时只有堆尾的节点是未排序的, 将添加的节点迭代到正确的位置 |
| 88 | + self._siftdown(0, n) |
| 89 | + |
| 90 | + def __repr__(self): |
| 91 | + vals = [str(i) for i in self._heap] |
| 92 | + return '>'.join(vals) |
| 93 | + |
| 94 | + |
| 95 | +if __name__ == '__main__': |
| 96 | + h = Heap([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) |
| 97 | + h.heapify() |
| 98 | + print(h) |
| 99 | + print(h.heappop()) |
| 100 | + print(h) |
| 101 | + h.heappush(3.5) |
| 102 | + print(h) |
| 103 | + h.heappush(0.1) |
| 104 | + print(h) |
| 105 | + h.heappush(0.5) |
| 106 | + print(h) |
| 107 | + print(h.heappop()) |
| 108 | + print(h) |
0 commit comments