完全二叉树
完全二叉树是满二叉树的一种退化。它只缺少最后一排最右边的一些元素。如下图所示。
完全二叉树的特性
完全二叉树有一个优秀的特性。就是它可以保存在一个数组中,而不需要使用链表的方式来存储。
如果对完全二叉树中的节点编号,那么可以总结出一个规律。
如果父节点的编号是 i,那么它的左子节点的编号就是 2i,右子节点的编号就是 2i+1
实际上,在计算机中,我们会把数组作为完全二叉树的实际存储结构,而完全二叉树,则是我们重新看待这段数组信息的思维逻辑结构。因此,数据结构最大的价值,就是对我们思维逻辑结构的改造。
堆
堆(Heap)是一个可以被看成近似完全二叉树的数组。树上的每一个结点对应数组的一个元素。除了最底层外,该树是完全充满的,而且是从左到右填充。—— 来自:《算法导论》
堆其实就是使用完全二叉树实现的一种数据结构。分为大顶堆和小顶堆。
- 小顶堆;如果一个完全二叉树的每一个父节点的值都小于其子节点的值,那么就是一个小顶堆。
- 大顶堆;如果一个完全二叉树的每一个父节点的值都大于其子节点的值,那么就是一个大顶堆。
为了让你更好地学习堆这种数据结构,我要和你分享一个学习数据结构的公式:数据结构 = 结构定义 + 结构操作。结构定义和结构操作是组成数据结构最重要的两个部分,也是你之后在学任何一种数据结构时的重点内容。结构定义就是定义一种性质,结构操作就是维护这种性质。
堆的结构定义
- 大顶堆可以维护一个集合中的最大值。
- 小顶堆可以维护一个集合中的最小值。
堆的结构操作
- 插入新元素
- 删除最值元素
- 将最后一个元素覆盖堆顶元素(数组尾元素覆盖数组头元素)
- 向下调整,维护堆的特性
堆排序
- 对数组建立一个大顶堆
- 每次将堆顶元素和堆尾元素调换位置(相当于把最大值放在了最后),然后减小堆的 size,从上到下维护堆
- 执行 n 次之后,得到一个从小到达排序的数组
优先队列
优先队列好像就是堆的别名啊?其实不然。你可以把优先队列当成是一种概念,那它的定义就是一种可以实现根据优先级出队的结构。而堆只是实现优先队列的其中一种方式,当然也是最普遍的方式。
实现一个最(小)大堆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
| abstract class Heap<T> { protected heap: T[] = []; protected getNumberForCompare: (val: T) => number;
protected abstract compare(indexA: number, indexB: number): boolean;
constructor(getNumberForCompare: (val: T) => number, values: T[] = []) { this.getNumberForCompare = getNumberForCompare; this.heapify(values); }
protected swap(indexA: number, indexB: number) { const temp = this.heap[indexA]; this.heap[indexA] = this.heap[indexB]; this.heap[indexB] = temp; }
protected getLeftChildIndex(index: number) { return 2 * index + 1; }
protected getRightChildIndex(index: number) { return 2 * index + 2; }
protected getParentIndex(index: number) { if (index === 0) return null; return (index - 1) >>> 1; }
protected heapify(values: T[]) { this.heap = values; for (let i = this.heap.length >>> 1; i >= 0; i--) { this.heapifyDown(i); } }
protected heapifyUp(index: number) { let currentIndex = index; let parentIndex = this.getParentIndex(currentIndex); while (parentIndex >= 0 && this.compare(currentIndex, parentIndex)) { this.swap(currentIndex, parentIndex); currentIndex = parentIndex; parentIndex = this.getParentIndex(parentIndex); } }
protected heapifyDown(index: number) { let currentIndex = index; let lChildIndex = this.getLeftChildIndex(currentIndex); let rChildIndex = this.getRightChildIndex(currentIndex);
const size = this.size();
if (lChildIndex < size && this.compare(lChildIndex, currentIndex)) { currentIndex = lChildIndex; }
if (rChildIndex < size && this.compare(rChildIndex, currentIndex)) { currentIndex = rChildIndex; }
if (currentIndex !== index) { this.swap(currentIndex, index); this.heapifyDown(currentIndex); } }
size() { return this.heap.length; }
isEmpty() { return this.heap.length === 0; }
insert(value: T): boolean { if (value != null) { this.heap.push(value); this.heapifyUp(this.heap.length - 1); return true; } return false; }
extract(): T { if (this.isEmpty()) return null;
if (this.size() === 1) return this.heap.shift();
this.swap(0, this.heap.length - 1); const top = this.heap.pop(); this.heapifyDown(0);
return top; }
top(): T { return this.isEmpty() ? null : this.heap[0]; }
values(): T[] { return [...this.heap]; } }
export class MinHeap<T> extends Heap<T> { protected compare(indexA: number, indexB: number): boolean { return ( this.getNumberForCompare(this.heap[indexA]) < this.getNumberForCompare(this.heap[indexB]) ); } }
export class MaxHeap<T> extends Heap<T> { protected compare(indexA: number, indexB: number): boolean { return ( this.getNumberForCompare(this.heap[indexA]) > this.getNumberForCompare(this.heap[indexB]) ); } }
|
参考