二叉堆是計算機科學中一種很是著名的數據結構,因爲它能高效、快速地找出最大值和最小值所以常被用於優先隊列和堆排序算法。javascript
本文將詳解二叉堆並用TypeScript將其實現,歡迎各位感興趣的開發者閱讀本文。java
本文重點講解堆如何實現,對堆這種數據結構不瞭解的開發者請移步個人另外一篇文章:數據結構:堆git
二叉堆是一種特殊的二叉樹,二叉堆也叫堆,它有如下兩個特性:github
一顆徹底二叉樹,它的每一層都有左側和右側子節點(除過最後一層的葉節點),而且最後一層的葉節點儘量都是左側子節點。算法
下圖描述了一顆徹底二叉樹: typescript
下圖描述了最大堆和最小堆 api
二叉堆有兩種表現方式:數組
下圖描述了兩種不一樣的表示方式 數據結構
咱們使用數組來表示二叉堆,對於給定位置(index)的節點,咱們能夠對其進行以下操做:函數
向堆中插入數據(insert)是指將數據插入堆的底部葉節點再執行上移(siftUp), 表示咱們將要把這個數據和它的父節點進行交換,直到父節點小於這個插入的值。
上移操做的實現以下:
交換的實現以下:
接下來咱們用一個例子來描述上述插入過程,以下圖所示爲一個最小堆,咱們要插入一個新的節點2。
移除最小值(最小堆)或最大值(最大堆)表示移除數組中的第一個元素(堆的根節點)。
在移除後,咱們須要將堆的最後一個元素移動至根部並執行下移(siftDown)函數,表示咱們將交換元素直到堆的結構正常。
下移操做的實現:
接下來,咱們經過一個例子來說解上述執行過程,下圖描述了一個最小堆
上述操做咱們實現了一個最小堆,最大堆與最小堆的別就在於節點的比較,所以咱們只須要繼承最小堆,重寫比對函數,將原來的a與b比較,改成b與a比較便可。
上面咱們講解了堆的概念,分析了的實現思路,接下來咱們將上述實現思路轉化爲代碼
export class MinHeap<T> {
// 用數組來描述一個堆
protected heap: T[];
constructor(protected compareFn: ICompareFunction<T> = defaultCompare) {
this.heap = [];
}
}
複製代碼
// 獲取左子節點的位置
protected getLeftIndex(index: number): number {
return 2 * index + 1;
}
// 獲取右子節點的位置
protected getRightIndex(index: number): number {
return 2 * index + 2;
}
// 獲取父節點的位置
protected getParentIndex(index: number): number | undefined {
if (index === 0) {
return undefined;
}
return Math.floor((index - 1) / 2);
}
複製代碼
insert(value: T): boolean {
if (value != null) {
// 向堆的葉結點添加元素,即數組的尾部
this.heap.push(value);
// 進行上移操做,即上移節點至合適的位置
this.siftUp(this.heap.length - 1);
return true;
}
return false;
}
// 實現上移函數
protected siftUp(index: number): void {
// 獲取父節點位置
let parent = <number>this.getParentIndex(index);
// 插入的位置必須大於0,且它的父節點大於其自己就執行循環裏的操做
while (index > 0 && this.compareFn(this.heap[parent], this.heap[index]) === Compare.BIGGER_THAN) {
// 交換元素的位置
this.swap(this.heap, parent, index);
// 修改當前插入值的位置爲它的父節點,從新獲取父節點的位置,即重複這個過程直到堆的根節點也通過了交換
index = parent;
parent = <number>this.getParentIndex(index);
}
}
// 實現交換數組元素位置函數
protected swap(array: T[], exchangeElement: number, exchangedElement: number): void {
// 用一個臨時變量保存交換元素
const temp = array[exchangeElement];
// 將被交換元素賦值給交換元素
array[exchangeElement] = array[exchangedElement];
// 將第一步保存的臨時變量賦值給被交換元素
array[exchangedElement] = temp;
}
複製代碼
findMinimum(): T | undefined {
// 返回數組的最小元素
return this.isEmpty() ? undefined : this.heap[0];
}
// 判斷堆是否爲空
isEmpty(): boolean {
return this.size() === 0;
}
複製代碼
extract(): T | undefined {
if (this.isEmpty()) {
return undefined;
}
if (this.size() === 1) {
// 返回數組的第一個元素
return this.heap.shift();
}
const removedValue = this.heap.shift();
// 執行下移操做
this.siftDown(0);
return removedValue;
}
// 下移操做
protected siftDown(index: number): void {
// 保存當前插入值的位置
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;
}
// 找到最小子節點的位置,校驗它的值是否和element相同
if (index !== element) {
// 若是不相同將它和最小的element進行交換
this.swap(this.heap, index, element);
// 遞歸執行
this.siftDown(element);
}
}
複製代碼
完整代碼地址:Heap.ts
堆的一種應用就是堆排序,此處不講解堆排序的實現思路,對堆排序不瞭解的開發者請移步個人另外一篇文章: 排序算法:堆排序的理解與實現
heapSort(array: T[]): void {
// 構建堆
this.buildHeap(array);
// 從堆的末尾開始遍歷,將遍歷到的元素與0好元素進行交換,而後執行下移操做
for (let i = array.length - 1; i >= 0; i--) {
this.swap(array, i, 0);
this.heapify(array, i, 0);
}
}
// 構建堆
private buildHeap(array: T[]) {
// 獲取最後一個節點的位置
const last = array.length - 1;
const lastParent = <number>this.getParentIndex(last);
// 從最後一個節點的父節點開始進行heapify操做
for (let i = lastParent; i >= 0; i--) {
this.heapify(array, array.length, i);
}
}
// 交換節點
private heapify(array: T[], size: number, index: number) {
// 遞歸基線條件
if (index >= size) {
return false;
}
// 找到當前要操做節點的左、右子樹
const left = this.getLeftIndex(index);
const right = this.getRightIndex(index);
// 保存當前要操做節點的位置
let element = index;
// 若是當前要操做節點的左子節點大於其父節點,更新element的值
if (left < size && this.compareFn(array[left], array[element]) === Compare.BIGGER_THAN) {
element = left;
}
// 若是當前要操做節點的右子節點大於其父節點,更新element的值
if (right < size && this.compareFn(array[right], array[element]) === Compare.BIGGER_THAN) {
element = right;
}
// element的位置不等於當前要操做節點,交換元素位置,遞歸執行
if (element !== index) {
this.swap(array, element, index);
this.heapify(array, size, element);
}
}
複製代碼
接下來咱們測試下上述代碼是否正常執行
import { MinHeap, MaxHeap } from "./lib/Heap.ts";
const minHeap = new MinHeap();
minHeap.insert(13);
minHeap.insert(10);
minHeap.insert(5);
minHeap.insert(7);
minHeap.insert(4);
minHeap.insert(17);
console.log("堆(min)的全部元素", minHeap.getIsArray());
console.log("堆(min)的最小值", minHeap.findMinimum());
console.log(minHeap.extract());
console.log(minHeap.getIsArray());
console.log("---------------------------------------");
const maxHeap = new MaxHeap();
maxHeap.insert(13);
maxHeap.insert(10);
maxHeap.insert(5);
maxHeap.insert(7);
maxHeap.insert(4);
maxHeap.insert(17);
console.log("堆(max)的全部元素", maxHeap.getIsArray());
console.log(maxHeap.extract());
console.log("堆(max)的最大值", maxHeap.findMinimum());
console.log("---------------------------------------");
const arrayTest = [12, 15, 17, 18, 4, 5, 1, 7, 19, 20];
minHeap.heapSort(arrayTest);
console.log(arrayTest);
複製代碼