什么是二叉堆 二叉堆本质是一种完全二叉树,二叉堆不是最小堆 就是最大堆 。它能高效、快速地找出最大值和最小值,常用于优先队列和堆排序算法。
完全二叉树 完全二叉树是二叉堆的结构特性 。一颗完全的二叉树,它的每一层都有左侧和右侧子节点(除最后一层的叶节点),并且最后一层的叶节点尽可能都是左侧子节点。完全二叉树约定编号从根结点起,自上而下,自左而右进行编号。
完全二叉树是指最后一层左边是满的,右边可能满也可能不满,然后其余层都是满的二叉树称为完全二叉树。
完全二叉树的比较: 例如下图a、b、c是完全二叉树,而d、e、f不是。
最小堆 根节点的键值是所有堆节点键值中最小的,且每个父节点的值都比子节点的值小
最大堆 根节点的键值是所有堆节点键值中最大的,且每个父节点的值都比子节点的值大
实现二叉堆 实现二叉堆有两种表示方式:
像二叉树一样用指针(节点)来表示
使用一个数组,通过索引值检索父节点、左侧和右侧子节点的值
操作堆节点 对于给定位置的堆节点:
它的左侧子节点位置是: 2 * index + 1 (如果位置可用)
它的右侧子节点位置是: 2 * index + 2 (如果位置可用)
它的父节点位置是: index / 2 (如果位置可用)
定义比较函数 实现二叉堆需要与左右侧子节点、父节点进行比较,所以需要定义一组用于节点比较的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 export const Compare = { LESS_THAN : -1 , BIGGER_THAN : 1 , EQUALS : 0 };export function reverseCompare (compareFn ) { return (a, b ) => compareFn (b, a); }export function defaultCompare (a, b ) { if (a === b) { return Compare .EQUALS ; } return a < b ? Compare .LESS_THAN : Compare .BIGGER_THAN ; }
实现最小堆 声明最小堆类 1 2 3 4 5 6 7 8 9 10 export class MinHeap { constructor (compareFn = defaultCompare ) { this .compareFn = compareFn; this .heap = []; } }
声明操作堆节点的方法 对于给定位置的堆节点,它的左侧子节点位置是: 2 * index + 1 (如果位置可用)
1 2 3 4 5 6 7 8 9 getLeftIndex (index ) { return (2 * index) + 1 ; }
对于给定位置的堆节点,它的右侧子节点位置是: 2 * index + 2 (如果位置可用)
1 2 3 4 5 6 7 8 9 getRightIndex (index ) { return (2 * index) + 2 ; }
对于给定位置的堆节点,它的父节点位置是: index / 2 (如果位置可用)
1 2 3 4 5 6 7 8 9 10 11 12 13 getParentIndex (index ) { if (index === 0 ) { return undefined ; } return Math .floor ((index - 1 ) / 2 ); }
获取堆的大小 1 2 3 4 5 6 7 size ( ) { return this .heap .length ; }
获取堆是否为空 1 2 3 4 5 6 7 isEmpty ( ) { return this .size () <= 0 ; }
向堆中插入新数据 向堆中插入值是指将值插入堆的底部叶节点,再执行上移操作(上移操作使用的是shiftUp方法,它会将当前值与它的父节点进行交换,直到父节点小于这个插入的值)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 insert (value ) { if (value != null ) { const index = this .heap .length ; this .heap .push (value); this .siftUp (index); return true ; } return false ; }
上移操作(shiftUp方法) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 siftUp (index ) { let parent = this .getParentIndex (index); while ( index > 0 && this .compareFn (this .heap [parent], this .heap [index]) === Compare .BIGGER_THAN ) { swap (this .heap , parent, index); index = parent; parent = this .getParentIndex (index); } }
交换操作(swap方法) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 export function swap (array, a, b ) { [array[a], array[b]] = [array[b], array[a]]; }
寻找堆中的最小值(最小堆)或最大值(最大堆)
在最小堆中,最小值总位于数组的第一个位置(堆的根节点)
在最大堆中,最大值总位于数组的第一个位置(堆的根节点)
1 2 3 4 5 6 7 findMinimum ( ) { return this .isEmpty () ? undefined : this .heap [0 ]; }
导出堆的最小值(最小堆)或最大值(最大堆) 移除最小值(最小堆的根节点)或最大值(最大堆的根节点),在移除后,将堆的最后一个元素移动至根节点,并执行下移操作(下移操作使用的是 siftDown 方法,它将交换元素直到堆的结构正常),最后返回被移除的最小值(最小堆)或最大值(最大堆)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 extract ( ) { if (this .isEmpty ()) { return undefined ; } if (this .size () === 1 ) { return this .heap .shift (); } const removedValue = this .heap [0 ]; this .heap [0 ] = this .heap .pop (); this .siftDown (0 ); return removedValue; }
下移操作(siftDown方法) 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 siftDown (index ) { let element = index; const left = this .getLeftIndex (index); const right = this .getRightIndex (index); const size = this .size (); if ( left < size && this .compareFn (this .heap [element], this .heap [left]) === Compare .BIGGER_THAN ) { element = left; } if ( right < size && this .compareFn (this .heap [element], this .heap [right]) === Compare .BIGGER_THAN ) { element = right; } if (index !== element) { swap (this .heap , index, element); this .siftDown (element); } }
清空整个堆 1 2 3 4 5 6 ## clear ( ) { this .heap = []; }
实现最大堆 最大堆的算法和最小堆的双发一模一样,不同在于节点的比较,因此我们实现最大堆可以通过继承最小堆的堆类,将比较反转,不将a和b进行比较,而是将b和a进行比较。
创建最大堆类 1 2 3 4 5 6 7 8 9 10 11 export class MaxHeap extends MinHeap { constructor (compareFn = defaultCompare ) { super (compareFn); this .compareFn = compareFn; this .compareFn = reverseCompare (compareFn); } }
反转比较函数 1 2 3 4 export function reverseCompare (compareFn ) { return (a, b ) => compareFn (b, a); }
完整代码 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 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 export const Compare = { LESS_THAN : -1 , BIGGER_THAN : 1 , EQUALS : 0 };export function reverseCompare (compareFn ) { return (a, b ) => compareFn (b, a); }export function defaultCompare (a, b ) { if (a === b) { return Compare .EQUALS ; } return a < b ? Compare .LESS_THAN : Compare .BIGGER_THAN ; }export function swap (array, a, b ) { [array[a], array[b]] = [array[b], array[a]]; }export class MinHeap { constructor (compareFn = defaultCompare ) { this .compareFn = compareFn; this .heap = []; } getLeftIndex (index ) { return (2 * index) + 1 ; } getRightIndex (index ) { return (2 * index) + 2 ; } getParentIndex (index ) { if (index === 0 ) { return undefined ; } return Math .floor ((index - 1 ) / 2 ); } size ( ) { return this .heap .length ; } isEmpty ( ) { return this .size () <= 0 ; } clear ( ) { this .heap = []; } findMinimum ( ) { return this .isEmpty () ? undefined : this .heap [0 ]; } insert (value ) { if (value != null ) { const index = this .heap .length ; this .heap .push (value); this .siftUp (index); return true ; } return false ; } siftDown (index ) { let element = index; const left = this .getLeftIndex (index); const right = this .getRightIndex (index); const size = this .size (); if ( left < size && this .compareFn (this .heap [element], this .heap [left]) === Compare .BIGGER_THAN ) { element = left; } if ( right < size && this .compareFn (this .heap [element], this .heap [right]) === Compare .BIGGER_THAN ) { element = right; } if (index !== element) { swap (this .heap , index, element); this .siftDown (element); } } siftUp (index ) { let parent = this .getParentIndex (index); while ( index > 0 && this .compareFn (this .heap [parent], this .heap [index]) === Compare .BIGGER_THAN ) { swap (this .heap , parent, index); index = parent; parent = this .getParentIndex (index); } } extract ( ) { if (this .isEmpty ()) { return undefined ; } if (this .size () === 1 ) { return this .heap .shift (); } const removedValue = this .heap [0 ]; this .heap [0 ] = this .heap .pop (); this .siftDown (0 ); return removedValue; } heapify (array ) { if (array) { this .heap = array; } const maxIndex = Math .floor (this .size () / 2 ) - 1 ; for (let i = 0 ; i <= maxIndex; i++) { this .siftDown (i); } return this .heap ; } getAsArray ( ) { return this .heap ; } }export class MaxHeap extends MinHeap { constructor (compareFn = defaultCompare ) { super (compareFn); this .compareFn = compareFn; this .compareFn = reverseCompare (compareFn); } }
后记 文章部分二叉堆图片来源于B站Up主:动画讲编程