算法|最大堆、最小堆和堆排序的实现(JavaScript)

一些概念

  • 堆:特殊的完全二叉树,具有特定性质的完全二叉树。
  • 大根堆:父节点 > 子节点
  • 小根堆:父节点 < 子节点

二叉堆也属于完全二叉树,所以可以用数组表示。

  • 若下标从1开始,左节点为 2*i ,右节点为 2*i+1 ,父节点为 i//2
  • 若下标从1开始,左节点为 2*i+1 ,右节点为 2*i+1+2 ,父节点为 (i-1)//2
    image.png

最大堆

两个重要方法,插入元素和移出元素。

  • 插入元素:在堆尾插入元素,调用辅助方法,将该元素上浮到正确位置。
  • 移出元素:将堆尾元素删去并替换到堆首,将该元素下沉到正确位置。

解释:

  • 上浮:如果父节点更大,则替换,循环直至比父节点小。
  • 下沉:如果子节点中较大的那个更小,则替换,循环直至子节点都比自身小。

实现

class MaxHeap {
   
    
    
	constructor() {
   
    
    
		this.heap = []
	}
	isEmpty() {
   
    
    
		return this.heap.length === 0
	}
	size() {
   
    
    
		return this.heap.length
	}
	#getParentIndex(idx) {
   
    
    
		return Math.floor((idx-1)/2)
	}
	#getLeft(idx) {
   
    
    
		return idx * 2 + 1
	}
	#getRight(idx) {
   
    
    
		return idx * 2 + 2
	}
	// 插入
	insert(v) {
   
    
    
		this.heap.push(v)
		this.#swim(this.size()-1)
	}
	// 删除最大值
	deleteMax() {
   
    
    
		const max = this.heap[0]
		this.#swap(0, this.size() - 1) // 将根和最后一个元素交换
		this.heap.pop() // 防止对象游离
		this.#sink(0) // 下沉,恢复有序性
		return max
	}
	// 第i个是否小于第j个
	#compare(a, b) {
   
    
    
			return a < b
	}
	// 交换
	#swap(i, j) {
   
    
    
		[this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]]
	}
	// 上浮
	#swim(k) {
   
    
    
		let parent = this.#getParentIndex(k)
		while(k > 0 && this.#compare(this.heap[parent], this.heap[k])) {
   
    
    
			this.#swap(parent, k)
			k = parent
			parent = this.#getParentIndex(k)
		}
	}
	// 下沉
	#sink(k) {
   
    
    
		while (this.#getLeft(k) < this.size()) {
   
    
    
			let j = this.#getLeft(k)
			// j 指向子节点的较大值
			if (j+1 < this.size() && this.#compare(this.heap[j], this.heap[j+1])) j++
			// 如果子节点都小
			if (this.#compare(this.heap[j], this.heap[k])) break
			this.#swap(k, j)
			k = j
		}
	}
}

测试

const mh = new MaxHeap()
mh.insert(20)
mh.insert(80)
mh.