# 二叉堆与优先队列

  • 二叉堆是一种完全二叉树,分为最小堆,和最大堆。
  • 最小堆即指任何一个父节点的值,都小于或等于它左右孩子的值,最大堆即相反。
  • 基于二叉堆,可以实现优先队列,即无论怎么入队,出队的总是最小/最大的值。
  • 其算法本质是:将二叉堆映射到一个一维数组,所有的动作都是对这个数组进行操作。
  • 下面以最小优先队列为例,来进行实现:

/**
 * 标题:最小二叉堆的上浮调整
 * 描述:从尾部开始,自动与父节点比大小,逐渐上浮到合适位置。
 */
function upAdjust (arr) {

    // 获取子下标,及其父下标
    let childIndex = arr.length - 1;
    let parentIndex = Math.floor((childIndex - 1) / 2);

    // 获取子值
    let child = arr[childIndex];

    // 逐步向上比较,一旦比父值小
    while ( childIndex > 0 && child < arr[parentIndex] ) {

        // 子值改为父值
        arr[childIndex] = arr[parentIndex];

        // 子下标上移一层
        childIndex = parentIndex;

        // 父下标上移一层
        parentIndex = Math.floor((parentIndex - 1) / 2);
    }

    // 落地子值
    arr[childIndex] = child;
    return arr;
}

/**
 * 标题:最小二叉堆的下沉调整
 * 描述:从头部开始,自动与子节点比大小,逐渐下沉到合适位置。
 */
function downAdjust (arr, parentIndex=0, length=arr.length) {

    // 父值设为父标值
    let parent = arr[parentIndex];

    // 获取子标为左子标
    let childIndex = 2 * parentIndex + 1;

    // 如果子标合法
    while ( childIndex < length ) {

        // 如果右子标也合法,且值更小,则子标改为右子标
        if (childIndex + 1 < length && arr[childIndex + 1] < arr[childIndex] ) {
            childIndex += 1;
        }

        // 如果父值小于任何子值,直接跳出,不再深入
        if ( parent <= arr[childIndex]) {
            break;
        }

        // 父值改为子值
        arr[parentIndex] = arr[childIndex];

        // 父标改为子标
        parentIndex = childIndex;

        // 子标改为孙子标
        childIndex  = 2 * parentIndex + 1;
    }

    // 落地父值
    arr[parentIndex] = parent;
    return arr;
}

/**
 * 标题:将无序堆调整为最小堆
 * 描述:从最后一个父结点开始,让所有的父结点依次“下沉”
 */
function buildHeap(arr){
    for ( let i = Math.floor((arr.length - 2) / 2); i >=0; i--) {
        downAdjust(arr, i, arr.length)
    }
}

/**
 * 标题:优先队列
 * 描述:利用最小堆实现
 * 初始算法:将无序堆调整为最小堆
 * 入队算法:添加到尾部(下一个叶子节点),然后自动向上调整
 * 出队算法:用队尾替换队头,然后自动向下调整,返回旧的队头
 */
class PriorityQueue {

  // 初始化
  constructor(array){
    this.array = array || []
    buildHeap(this.array)
  }

  // 入队
  push(key){
    this.array.push(key)
    upAdjust(this.array)
    return this;
  }

  // 出队
  pop(){
    let len = this.array.length;
    if (len == 0) return undefined;
    if (len == 1) return this.array.pop();

    const key = this.array[0]
    this.array[0]=this.array[len - 1]
    this.array.length = len - 1;
    downAdjust(this.array)
    return key;
  }

  toString(){
    return this.array.join(',')
  }
}

// 测试
let arr = new PriorityQueue([1,3,5,7,2,0,9,5])
console.log(arr.pop(), arr)
console.log(arr.pop(), arr)




更新时间: 4/30/2020, 4:20:05 AM