第十一章 二叉堆

簡介

二叉堆是一種特殊的二叉樹,可是不是一個二叉搜索樹,二叉堆 是計算機科學中很是著名的數據結構,又稱,因爲其高效,快速地查找出最大值和最小值,經常使用於優先隊列typescript

結構

  • 他是一棵徹底二叉樹,表示樹的每一層都有左側和右側子節點(除了最後一層的葉子節點), 而且最後一層的葉節點儘量在都在左側,這個叫作結構特徵
  • 二叉堆不是最小堆就是最大堆.最小堆運行你快速導出最小值,最大堆容許你快速導出樹的最大值.全部的節點都大於等於(最大堆),或小於等於(最小堆)每一個它的子節點.這叫做堆特性.

儘管二叉堆是二叉樹,但並不必定是二叉搜索樹(BST).在二叉堆中,每一個子節點都要大於等於父節點(最小堆)或小於等於父節點(最大堆).然而二叉搜索樹中,左側子節點老是比父節點小,右側子節點也老是更大api

建立咱們的最小堆

選擇存儲數據的數據類型

這裏能夠選擇和上一章樹同樣的數據類型(雙指針,一個左指針,一個右指針),可是也可使用數組數組

左節點: 當前節點下標 * 2 + 1數據結構

右節點:當前節點下標 * 2 + 2學習

父節點: (index - 1) / 2 (當位置可用時,這是一個邊界,注意 )ui

基礎方法

insert(value) : 這個方法向堆中插入一個新的值.若是插入成功,它返回true,不然返回falsethis

extract(): 這個方法移除最小值(最小堆的時候)或最大值(最大堆的時候),並返回這個值指針

findMinimum():這個方法返回最小值(最小堆的時候)或最大值(最大堆的時候),但不會刪除該值code

基礎方法

enum Compare {
  LESS_THAN = -1,
  BIGGER_THAN = 1,
  EQUALS = 0
}

function defaultCompareFn<T>(parent: T, child: T) {
  if (parent === child) {
    return Compare.EQUALS
  }
  return parent > child ? Compare.BIGGER_THAN : Compare.LESS_THAN;
}
// 數組交換
function swap<T>(array: Array<T>, index: number, otherIndex: number) {
  [array[index], array[otherIndex]] = [array[otherIndex], array[index]];
}

這裏添加一個枚舉類型,表示小於,大於,等於blog

添加一個默認比較方法

添加一個數組交換的方法,由於常常要進行數組交換

基礎結構

export class MinHeap<T> {
  private heap: Array<T>;
  compareFn: Function;

  constructor(compareFn: Function = defaultCompareFn) {
    //使用數組必定程度上不用去維護左右的指針
    this.heap = [];
    //這個是一個比較大小的方法,與前面相似
    this.compareFn = compareFn;
  } 
    
  //獲取左邊子元素位置
  getLeftIndex(index: number) {
    return index * 2 + 1;
  }

  //獲取右邊子元素位置
  getRightIndex(index: number) {
    return index * 2 + 2;
  }

  //獲取父節點元素位置
  getParentIndex(index: number) {
    if (index === 0) {
      return undefined;
    }
    return Math.floor((index - 1) / 2);
  }

  isEmpty() {
    return this.heap.length <= 0;
  }

  size() {
    return this.heap.length;
  }

  clear() {
    this.heap = [];
  }
}

這裏是用的數組作存儲,至於左右指針就以上面說的那樣算

因此提供一個獲取左右指針以及父指針的方法 + 之前常常用的方法

方法實現

insert方法

// 插入值
  insert(value: T) {
    if (value != null) {
      //先插入到堆裏去吧,這個是插入到最後,因此還要看這個數是否在這個位置
      this.heap.push(value);
      //上移操做
      this.siftUp(this.heap.length - 1);
      return true;
    }
    return false;
  }

這裏是把值先push到數組中,添加到最尾部了,可是這個並非說他就是在這個位置,因此須要進行數據上移操做

siftUp操做(遞歸版)

siftUp(index: number) { 
     //向上移動,因此與其父節點比較
     let pIndex = this.getParentIndex(index);
     //父級節點大於當前節點,那麼就要交換位置
     if (index > 0 && this.compareFn(this.heap[pIndex], this.heap[index]) === Compare.BIGGER_THAN) {
      //將當前元素與父元素交換
      swap(this.heap, pIndex, index);
      //交換完成後咱們要進行下一步比較,咱們插入的元素在pIndex位置,可是咱們不知道他是否到了該在的位置,因此繼續
      this.siftUp(pIndex);
    } 
  }

其實所謂的上移就是最簡單的比較,咱們如今弄最小堆,因此只要咱們的新插入的值 < 父節點值,那就說明咱們須要上移,那麼就交換位置,注意:這裏上移一個位置後,咱們的目標就改到了父節點上,因此咱們遞歸的index變成了pIndex

siftUp操做(迭代版)

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方法

移除最小值,基本思路就是

  • 交換最後一個和第一個的位置
  • 而後移除最後一個,並保存做爲返回值
  • 而後讓第一個值向下找合適的位置(這裏其實相似於上面的siftUp,區別不過是之前是一個數比較,如今子節點有兩個,可是要找到最小的那個,必定要記得是最小的那個哦!!!)
  • 爲何找最小的???[考慮第一次交換的狀況,當前元素須要下移,而且在第一個節點上,須要找一個最小的放上面,做爲根節點]
  • 而後交換位置,而後繼續下移,直到最後
extract() {
    //若是空,就不用移除了
    if (this.isEmpty()) {
      return undefined;
    }
    //若是有一個元素,就能夠直接移除返回
    if (this.size() === 1) {
      return this.heap.shift();
    }
    //最後一個和第一個交換
    swap(this.heap, 0 , this.size() - 1)
    //彈出最後一個,並保留做爲返回值
    let result = this.heap.pop();
    //開始向下移動
    this.siftDown(0);
    return result;
  }

siftDown操做

siftDown(index: number) {
    //獲取左右的下標
    let leftIndex = this.getLeftIndex(index);
    let rightIndex = this.getRightIndex(index);

    // 定義index值 element
    let element = index;

    // 在有效範圍內和左邊元素比較,若是大就向下移動
    if(index < this.size() && this.compareFn(this.heap[element], this.heap[leftIndex]) === Compare.BIGGER_THAN){
      element = leftIndex;
    }
    //***  在有效範圍內和右元素比較,若是大就向下移動   這裏使用的是element,注意,由於element表明了最小的數
    if(index < this.size() && this.compareFn(this.heap[element], this.heap[rightIndex]) === Compare.BIGGER_THAN){
      element = rightIndex;
    }

    //肯定位置結束
    if(element !== index){
      // 交換元素,下移當前元素
      swap(this.heap, index , element);
      // 而後遞歸,繼續移動當前元素,可是這個時候就再也不是index,而是element
      this.siftDown(element);
    }
  }

這裏必定要注意的是***標註點處的使用element,由於index可能在左節點的時候被替換了,這裏須要的是最小值

findMinimum方法

這個就不用多說了吧,就是取數組的第一個就好

//找最小的事
findMinimum() {
  return this.isEmpty() ? undefined : this.heap[0];
}

總結

到此的全部方法就實現了,總體來講堆的邊界不是很複雜,因此仍是比較簡單的一種

其餘

你說還沒結束,哦,好像是的,忘記了兩件事,一個最喜歡的貼代碼,一個就是堆排序

function heapify(array: any[], index: number, heapSize: number, compareFn: Function) {
  let largest = index;
  const left = (2 * index) + 1;
  const right = (2 * index) + 2;

  if (left < heapSize && compareFn(array[left], array[index]) > 0) {
    largest = left;
  }

  if (right < heapSize && compareFn(array[right], array[largest]) > 0) {
    largest = right;
  }

  if (largest !== index) {
    swap(array, index, largest);
    heapify(array, largest, heapSize, compareFn);
  }
}

function buildMaxHeap(array: any[], compareFn: Function) {
  for (let i = Math.floor(array.length / 2); i >= 0; i -= 1) {
    heapify(array, i, array.length, compareFn);
  }
  return array;
}

export function heapSort(array: any[], compareFn = defaultCompareFn) {
  let heapSize = array.length;
  buildMaxHeap(array, compareFn);
  while (heapSize > 1) {
    swap(array, 0, --heapSize);
    heapify(array, 0, heapSize, compareFn);
  }
  return array;
}

方法就很少BB了,加油

全部代碼

/*
*  二叉堆是二叉數,可是不必定是二叉搜索樹
*
*  insert(value)
*  extract():移除最大堆的最大值或者最小堆的最小值
*  findMinimum():這個方法返回最小值(最小堆)或最大值(最大堆)且不會移除這個值
*
*  二叉堆以數組存數據   下標找節點
*  初始節點:         0
*           1              2
*       3      4        5      6
*    7    8  9   10  11  12 13  14
*                                        0
* 第二排(下標1):         0 * 2 + 1                      0 * 2 + 2
* 第三排(下標2): 1 * 2 + 1      1 * 2 + 2      2 * 2 + 1        2 * 2 + 2
*
*總結:
*左元素的位置: 當前下標 * 2 + 1
*右元素的位置: 當前下標 * 2 + 2
*/
enum Compare {
  LESS_THAN = -1,
  BIGGER_THAN = 1,
  EQUALS = 0
}

function defaultCompareFn<T>(parent: T, child: T) {
  if (parent === child) {
    return Compare.EQUALS
  }
  return parent > child ? Compare.BIGGER_THAN : Compare.LESS_THAN;
}
// 數組交換
function swap<T>(array: Array<T>, index: number, otherIndex: number) {
  [array[index], array[otherIndex]] = [array[otherIndex], array[index]];
}

export class MinHeap<T> {
  private heap: Array<T>;
  compareFn: Function;

  constructor(compareFn: Function = defaultCompareFn) {
    this.heap = [];
    this.compareFn = compareFn;
  }

  //獲取左邊子元素位置
  getLeftIndex(index: number) {
    return index * 2 + 1;
  }

  //獲取右邊子元素位置
  getRightIndex(index: number) {
    return index * 2 + 2;
  }

  //獲取父節點元素位置
  getParentIndex(index: number) {
    if (index === 0) {
      return undefined;
    }
    return Math.floor((index - 1) / 2);
  }

  isEmpty() {
    return this.heap.length <= 0;
  }

  size() {
    return this.heap.length;
  }

  clear() {
    this.heap = [];
  }

  // 插入值
  insert(value: T) {
    if (value != null) {
      //先插入到堆裏去吧,這個是插入到最後,因此還要看這個數是否在這個位置
      this.heap.push(value);
      this.siftUp(this.heap.length - 1);
      return true;
    }
    return false;
  }



  //以數組的形式返回數據
  getAsArray() {
    return this.heap
  }

  // 向上移動數據
  siftUp(index: number) {
    /*
     //向上移動,因此與其父節點比較
     let pIndex = this.getParentIndex(index);
     //父級節點大於當前節點,那麼就要交換位置
     if (index > 0 && this.compareFn(this.heap[pIndex], this.heap[index]) === Compare.BIGGER_THAN) {
      //將當前元素與父元素交換
      swap(this.heap, pIndex, index);
      //交換完成後咱們要進行下一步比較,咱們插入的元素在pIndex位置,可是咱們不知道他是否到了該在的位置,因此繼續
      this.siftUp(pIndex);
    }
    */


    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);
    }
  }

  //找最小的事
  findMinimum() {
    return this.isEmpty() ? undefined : this.heap[0];
  }


  //移除堆中的最小值
  extract() {
    //若是空,就不用移除了
    if (this.isEmpty()) {
      return undefined;
    }
    //若是有一個元素,就能夠直接移除返回
    if (this.size() === 1) {
      return this.heap.shift();
    }
    //最後一個和第一個交換
    swap(this.heap, 0 , this.size() - 1)
    //彈出最後一個,並保留做爲返回值
    let result = this.heap.pop();
    //開始堆化
    this.siftDown(0);
    return result;
  }

  /**
   * 移動數據
   * 使第一個元素找到其正確的位置
   * 注意,此時的第一個元素是最大值(最小堆),因此這個時候只要向下找合適位置就能夠了
   *
   * 這個方法其實和向上找(siftUp)是基本同樣的,可是區別就在是有左右兩個節點,因此咱們基於前面說的原則,找到最小的那個便可
   */
  siftDown(index: number) {
    //獲取左右的下標
    let leftIndex = this.getLeftIndex(index);
    let rightIndex = this.getRightIndex(index);

    // 定義index值 element
    let element = index;

    // 在有效範圍內和左邊元素比較,若是大就向下移動
    if(index < this.size() && this.compareFn(this.heap[element], this.heap[leftIndex]) === Compare.BIGGER_THAN){
      element = leftIndex;
    }
    // 在有效範圍內和右元素比較,若是大就向下移動   這裏使用的是element,注意,由於element表明了最小的數
    if(index < this.size() && this.compareFn(this.heap[element], this.heap[rightIndex]) === Compare.BIGGER_THAN){
      element = rightIndex;
    }

    //肯定位置結束
    if(element !== index){
      // 交換元素,下移當前元素
      swap(this.heap, index , element);
      // 而後遞歸,繼續移動當前元素,可是這個時候就再也不是index,而是element
      this.siftDown(element);
    }
  }

  heapify(array: T[]) {
    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;
  }
}

function defaultMaxCompareFn<T>(parent: T, child: T) {
  if (parent === child) {
    return Compare.EQUALS
  }
  return parent > child ? Compare.LESS_THAN : Compare.BIGGER_THAN
}

export class MaxHeap<T> extends MinHeap<T> {
  constructor(compareFn = defaultMaxCompareFn) {
    super(compareFn);
  }
}


function heapify(array: any[], index: number, heapSize: number, compareFn: Function) {
  let largest = index;
  const left = (2 * index) + 1;
  const right = (2 * index) + 2;

  if (left < heapSize && compareFn(array[left], array[index]) > 0) {
    largest = left;
  }

  if (right < heapSize && compareFn(array[right], array[largest]) > 0) {
    largest = right;
  }

  if (largest !== index) {
    swap(array, index, largest);
    heapify(array, largest, heapSize, compareFn);
  }
}

function buildMaxHeap(array: any[], compareFn: Function) {
  for (let i = Math.floor(array.length / 2); i >= 0; i -= 1) {
    heapify(array, i, array.length, compareFn);
  }
  return array;
}

export function heapSort(array: any[], compareFn = defaultCompareFn) {
  let heapSize = array.length;
  buildMaxHeap(array, compareFn);
  while (heapSize > 1) {
    swap(array, 0, --heapSize);
    heapify(array, 0, heapSize, compareFn);
  }
  return array;
}

書本代碼

enum Compare {
  LESS_THAN = -1,
  BIGGER_THAN = 1,
  EQUALS = 0
}

function defaultCompare<T>(a: T, b: T): number {
  if (a === b) {
    return Compare.EQUALS;
  }
  return a < b ? Compare.LESS_THAN : Compare.BIGGER_THAN;
}

type ICompareFunction<T> = (a: T, b: T) => number;


function reverseCompare<T>(compareFn: ICompareFunction<T>): ICompareFunction<T> {
  return (a, b) => compareFn(b, a);
}

function swap(array: any[], a: number, b: number) {
  /* const temp = array[a];
  array[a] = array[b];
  array[b] = temp; */
  [array[a], array[b]] = [array[b], array[a]];
}

export class MinHeap<T> {
  protected heap: T[] = [];

  constructor(protected compareFn: ICompareFunction<T> = defaultCompare) {}

  private getLeftIndex(index: number) {
    return 2 * index + 1;
  }

  private getRightIndex(index: number) {
    return 2 * index + 2;
  }

  private getParentIndex(index: number) {
    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: T) {
    if (value != null) {
      const index = this.heap.length;
      this.heap.push(value);
      this.siftUp(index);
      return true;
    }
    return false;
  }

  private siftDown(index: number) {
    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);
    }
  }

  private siftUp(index: number): void {
    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: T[]) {
    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<T> extends MinHeap<T> {
  constructor(protected compareFn: ICompareFunction<T> = defaultCompare) {
    super(compareFn);
    this.compareFn = reverseCompare(compareFn);
  }
}

表示這一週原本是說兩個章節的,但是工做還有生活緣由(這都是假的,主要是本身蠢,後面的圖,迷迷糊糊的,唉!!!)

而後發現本身胖了,胖得不行了,因此下週開始減肥,而且滿滿的日程,可是學習不能斷呀!!!仍是會盡可能基礎時間學習,畢竟如今太弱雞了,唉着急!!!! 下週排序,有興趣的能夠留意我一下哦!!!

相關文章
相關標籤/搜索