# 二叉堆的基本性质

  • 它是一棵完全二叉树,即除了最后一层的叶节点外,其他每层都有左侧和右侧子节点;最后一层的叶节点尽可能都是左侧叶子节点。

  • 二叉堆不是最小堆就是最大堆。

# 最小堆的性质

所有的节点都小于等于它的子节点,即根结点是最小的,可以快速导出树的最小值。

# 二叉树的表示方式

  • 使用指针

  • 使用数组,通过索引值检索父节点,左侧和右侧子节点的值。

我们最小堆使用数组来表示,这样我们可以根据索引来检索它的父节点与子节点。

对于给定位置index的节点:

它的左侧子节点位置为:2 * index + 1

它的右侧子节点位置为:2 * index + 2

它的父节点位置为:Math.floor((index - 1) / 2)

# 最小堆的主要操作

  • insert(value):  向堆中插入一个新值,如果插入成功,返回 true,否则返回 false。

  • extract():  移除最小值,并返回这个值。

  • findMinimum():  返回最小值,但是不会移除。

# js实现最小堆

  • 代码实现
const Compare = {
    LESS_THAN: -1,
    BIGGER_THAN: 1,
    EQUAL: 0
}

function defaultCompare(a, b) {
    if (a === b) {
        return 0
    }
    return a < b ? -1 : 1
}

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.heap.length === 0
    }

    clear() {
        this.heap = []
    }


    // 返回最小值,但是不会移除
    findMinimum() {
        return this.isEmpty() ? undefined : this.heap[0]
    }

    // 向堆中插入一个新值
    insert(value) {
        if (value !== null && value !== undefined) {
            this.heap.push(value)
            this.siftUp(this.heap.length - 1) // 上移,保证堆的特性
            return true
        }
        return false
    }

    // 上移操作
    siftUp(index) {
        let currentIndex = index
        let parentIndex = this.getParentIndex(index)
        while (currentIndex > 0 && this.compareFn(this.heap[parentIndex], this.heap[currentIndex]) === Compare.BIGGER_THAN) {
            this.swap(this.heap, parentIndex, currentIndex)
            currentIndex = parentIndex
            parentIndex = this.getParentIndex(currentIndex)
        }
    }

    // 交换数组的两个元素
    swap(array, a, b) {
        const temp = array[a]
        array[a] = array[b]
        array[b] = temp
    }

    // 移除最小值,并返回这个值
    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(index) {
        let currentIndex = index
        const left = this.getLeftIndex(currentIndex)
        const right = this.getRightIndex(currentIndex)
        const size = this.size()
        if (left < size && this.compareFn(this.heap[currentIndex], this.heap[left]) === Compare.BIGGER_THAN) {
            currentIndex = left
        }
        if (right < size && this.compareFn(this.heap[currentIndex], this.heap[right]) === Compare.BIGGER_THAN) {
            currentIndex = right
        }
        if (currentIndex !== index) {
            this.swap(this.heap, currentIndex, index)
            this.siftDown(currentIndex)
        }
    }
}

  • 注意点

为了保证最小堆的特性(所有的节点都小于等于它的子节点),我们的堆有一个上移方法,一个下沉方法。

每次插入新值的时候,我们都是首先在末尾添加,然后进行上移,找到新值的正确位置。

每次移除最小值的时候,我们先取出堆顶的值,然后将末尾元素pop,放在堆顶,进行下沉,找到末尾元素的正确位置,最后返回我们之前取出的堆顶值。

# 参考文章

  • javascript数据结构与算法第三版