堆知识小窥
1. 堆的概念
堆是基于完全二叉树的一种数据结构,就是连续的一片存储空间。
插入知识 深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树
2. 堆的分类
堆可以分为大顶堆和小顶堆。
- 大顶堆:任意一个节点都比他的子节点大。所以大顶堆的最大值就是根节点
- 小顶堆:任意一个节点都比他的子节点小。所以小顶堆的最小值就是根节点
3. 堆的操作
堆的操作分为插入和弹出
- 插入
- 大顶堆:当前插入的节点和父节点进行比较,如果当前插入的节点比父节点大,那么调换他们的位置,以此类推
- 小顶堆:当前插入的节点和父节点进行比较,如果当前插入的节点比父节点小,那么调换他们的位置,以此类推
- 弹出
- 大顶堆:将最后一个节点放在根节点,然后将新的根节点和子节点进行比较,将三个节点总最大的放到根节点,如果产生交换,那么将交换过的原根节点,也就是现在的新子节点,然后将新子节点作为新的根节点,然后和新的根节点的子节点进行上述操作的递归处理
- 小顶堆:将最后一个节点放在根节点,然后将新的根节点和子节点进行比较,将三个节点总最小的放到根节点,如果产生交换,那么将交换过的原根节点,也就是现在的新子节点,然后将新子节点作为新的根节点,然后和新的根节点的子节点进行上述操作的递归处理
4. 堆的代码实现
- 大顶堆
class BigHeap {
constructor () {
// 用于存储堆中的数据
this.arr = []
// 堆中的元素的数量
this.count = 0
}
// 交换arr中的两个数据
swap (a, b) {
const temp = this.arr[a]
this.arr[a] = this.arr[b]
this.arr[b] = temp
}
// 将元素放入堆中
push (val) {
this.arr.push(val)
this.count++
let index = this.count - 1
// 找到父元素,然后和父元素进行比较,如果比父元素大
while (index && this.arr[Math.floor((index - 1) / 2)] < this.arr[index]) {
this.swap(Math.floor((index - 1) / 2), index)
index = Math.floor((index - 1) / 2)
}
}
// 将堆顶元素推出
pop () {
if (this.count === 0) return null
const res = this.top()
// 将最后一个元素放置到首位置
this.arr[0] = this.arr.pop()
this.count--
let index = 0
// 堆中的最大下标
const count = this.count - 1
// 表明 index 有子节点
while (index * 2 + 1 <= count) {
// temp 用来存储以 index 作为根节点所形成的三角区域中最大的元素的下标
let temp = index
if (this.arr[index * 2 + 1] > this.arr[index]) {
// 更新 temp
temp = index * 2 + 1
}
if (
count >= index * 2 + 2 &&
this.arr[temp] < this.arr[index * 2 + 2]
) {
// 更新 temp
temp = index * 2 + 2
}
// 如果以 index 作为根节点所形成的三角区域中最大的元素就是 index ,则没有必要往下继续比较
if (temp === index) break
this.swap(index, temp)
// 继续以新的 index 作为根节点,然后比较三角区域
index = temp
}
return res
}
// 返回堆顶元素
top () {
if (this.count === 0) return null
return this.arr[0]
}
}
const big = new BigHeap()
big.push(3)
big.push(4)
big.push(5)
big.push(6)
console.log(big.pop())
console.log(big.pop())
console.log(big.pop())
console.log(big.pop())
- 小顶堆
class SmallHeap {
constructor () {
// 用于存储堆中的数据
this.arr = []
// 堆中的元素的数量
this.count = 0
}
// 交换arr中的两个数据
swap (a, b) {
const temp = this.arr[a]
this.arr[a] = this.arr[b]
this.arr[b] = temp
}
// 将元素放入堆中
push (val) {
this.arr.push(val)
this.count++
let index = this.count - 1
// 比较和父元素的大小,如果比父元素小
while (index && this.arr[Math.floor((index - 1) / 2)] > this.arr[index]) {
this.swap(Math.floor((index - 1) / 2), index)
index = Math.floor((index - 1) / 2)
}
}
// 将堆顶元素推出
pop () {
if (this.count === 0) return null
const res = this.top()
// 将最后一个元素放置到首位置
this.arr[0] = this.arr.pop()
this.count--
let index = 0
// 堆中的最大下标
const count = this.count - 1
// 表明 index 有子节点
while (index * 2 + 1 <= count) {
// temp 用来存储以 index 作为根节点所形成的三角区域中最小的元素的下标
let temp = index
if (this.arr[index * 2 + 1] < this.arr[index]) {
// 更新 temp
temp = index * 2 + 1
}
if (
count >= index * 2 + 2 &&
this.arr[temp] > this.arr[index * 2 + 2]
) {
// 更新 temp
temp = index * 2 + 2
}
// 如果以 index 作为根节点所形成的三角区域中最大的元素就是 index ,则没有必要往下继续比较
if (temp === index) break
this.swap(index, temp)
// 继续以新的 index 作为根节点,然后比较三角区域
index = temp
}
return res
}
// 返回堆顶元素
top () {
if (this.count === 0) return null
return this.arr[0]
}
}
const small = new SmallHeap()
small.push(3)
small.push(4)
small.push(6)
small.push(5)
small.push(2)
console.log(small.pop())
console.log(small.pop())
console.log(small.pop())
console.log(small.pop())
console.log(small.pop())
5. 堆的应用场景
堆适合处理的场景:在一个集合中寻找最值。比如在一个数组中寻找最大值或者最小值
6. 堆的设计改进
上述设计的无论是大顶堆还是小顶堆,默认节点元素是数字,那么我们能不能将元素扩展成你需要的类型?看一下以上实现,如果要是扩展的话,那么应该是在比较大小的时候需要改进一下,把我们需要比较的函数传入即可。则: