堆
-
堆是一种特殊的完全二叉树
- 完全二叉树:每层节点都完全填满;最后一层,如果不是满的,则缺少右边的若干节点
-
堆:所有节点都大于等于(最大堆)或小于等于(最小堆)它的子节点
- 可见堆是一种有顺序的数据结构
- 如下图所示
-
最大堆示例
6 / \ 5 3 / \ / 4 2 1 -
最小堆示例
1 / \ 3 2 / \ / 4 6 5 -
堆的规律
- 一个堆,如果按广度优先遍历就是 0 1 2 3 4 5,我们把0 ~ 5作为数组的下标
- 并把节点中的值填到数组中,如此一来就可以把数组来表示一个堆了,不仅如此有公式可以获取子节点和父节点的位置关系
- 左侧子节点的位置是 2 * index + 1
- 右侧子节点的位置是 2 * index + 2
- 父节点的位置是 (index - 1) / 2 的商
-
在前端js中表示堆的示例: 这是一个最小堆的结构
1 / \ 3 6 / \ / 5 9 8 -
给这个最小堆标记下索引(下标),如下
1(0) / \ 3(1) 6(2) / \ / 5(3) 9(4) 8(5) -
上图括号中的就是在堆中的下标
-
这个最小堆在js中的表示形式是:[1,3,6,5,9,8], 数组中对应的各个元素的下标:0,1,2,3,4,5
- 这个规律就是按照广度优先遍历来走的
- 也就是从上到下,从左到右
-
堆的应用
- 快速高效找出最大值和最小值
- 时间复杂度O(1)
- 找出第k个最大(小)元素
-
解决第k个最大元素
- 构建一个最小堆,并将元素依次插入堆中
- 当堆的容量超过K,就删除堆顶
- 当插入结束后,堆顶就是第k个最大元素
-
如果要找到第k个最小元素,反过来就行,道理一致
实现一个最小堆类
- 在类里,声明一个数组,用来装元素
- 主要方法:插入,删除堆顶、获取堆顶、获取堆大小
- 插入
- 将值插入堆的底部,即数组的尾部
- 然后上移:将这个值和它的父节点进行交换换,直到父节点小于等于这个插入的值
- 大小为k的堆中插入元素的时间复杂度为O(logk)
- 复杂度主要体现在上移操作中,最多循环的次数是堆的高度,因为它不断上移
- 这个堆的高度也就是二叉树的高度,二叉树的高度和树节点数量的关系是logk
- 所以时间复杂度是O(logk)
- 删除堆顶
- 用数组尾部元素替换堆顶(直接删除堆顶会破坏堆结构)
- 然后下移,将新堆顶和它的子节点进行交换,直到子节点大于等于这个新堆顶
- 大小为k的堆中删除堆顶的事件复杂度为O(logk)
- 和插入一样的道理,下移次数最大是堆的高度,所以时间复杂度也是logk
- 获取堆顶:返回数组的头部
- 获取堆的大小:返回数组的长度
Typescript 版算法实现
1 ) 方案1
class MinHeap {
heap: number[] = [];
// 获取父节点
getParentIndex(i: number): number {
return (i - 1) >> 1;
}
// 获取左子节点
getLeftIndex(i: number): number {
return (i << 1) + 1;
}
// 获取右子节点
getRightIndex(i: number): number {
return (i << 1) + 2;
}
// 两数交换
swap(i: number, j: number): void {
// 写法1, 比较繁琐
// let temp = this.heap[i];
// this.heap[i] = this.heap[j];
// this.heap[j] = temp;
// 写法3 对象结构,别名的方式
// let a = 1,b = 2;
// {a:b,b:a} = {a,b}
// 写法2 通过数学运算加减关系,利用两个数字在坐标轴上的距离固定来交换
// let a = 1,b = 2;
// b = b - a; // b(原) - a(原)
// a = a + b; // a(原) + b(原) - a(原)
// b = a - b; // b(原) - (b(原) - a(原))
// console.log(a); //2
// console.log(b); //1
// 使用es6解构数组赋值来交换两数
[this.heap[j], this.heap[i]] = [this.heap[i], this.heap[j]];
}
// 上移操作封装 是个递归
shiftUp(i: number): void {
if (!i) return;
const pi = this.getParentIndex(i);
if (this.heap[pi] > this.heap[i]) {
this.swap(pi, i);
this.shiftUp(pi);
}
}
// 下移操作
shiftDown(i: number): void {
const size = this.size();
const li = this.getLeftIndex(i);
const ri = this.getRightIndex(i);
const cur = this.heap[i];
let smallest = i;
if (li < size && this.heap[li] < cur) {
smallest = li;
}
if (ri < size && this.heap[ri] < this.heap[smallest]) {
smallest = ri;
}
if (smallest !== i) {
this.swap(smallest, i);
this.shiftDown(smallest);
}
}
// 添加
insert(value: number): void {
this.heap.push(value);
this.shiftUp(this.heap.length - 1);
}
// 删除
pop(): void {
if (this.size() === 0) return;
this.heap[0] = this.heap.pop()!;
this.shiftDown(0);
}
// 获取堆顶
peak(): number | undefined {
return this.heap[0];
}
// 获取堆大小
size(): number {
return this.heap.length;
}
// 输出最后的堆结构, 有参,输出参数;无参,输出堆结构
print(o?: any): void {
console.log(o ? o : this.heap);
}
}
// 测试用例
const heap = new MinHeap();
// 测试插入操作
heap.insert(3);
heap.insert(1);
heap.insert(6);
heap.insert(5);
heap.insert(2);
heap.insert(4);
console.log("After insertions:");
heap.print(); // 应该输出 [1, 3, 2, 5, 6, 4]
// 测试获取堆顶元素
console.log("Peek:", heap.peak()); // 应该输出 1
// 测试删除堆顶元素
heap.pop();
console.log("After pop:");
heap.print(); // 应该输出 [2, 3, 4, 5, 6]
// 测试再次删除堆顶元素
heap.pop();
console.log("After another pop:");
heap.print(); // 应该输出 [3, 5, 4, 6]
// 测试获取堆的大小
console.log("Size:", heap.size()); // 应该输出 4
// 测试空堆的情况
const emptyHeap = new MinHeap();
console.log("Empty heap peek:", emptyHeap.peak()); // 应该输出 undefined
emptyHeap.pop();
console.log("Empty heap size after pop:", emptyHeap.size()); // 应该输出 0
Python3 版算法实现
1 ) 方案1
class MinHeap:
def __init__(self):
self.heap = []
# 获取父节点
def get_parent_index(self, i):
return (i - 1) >> 1
# 获取左子节点
def get_left_index(self, i):
return (i << 1) + 1
# 获取右子节点
def get_right_index(self, i):
return (i << 1) + 2
# 两数交换
def swap(self, i, j):
self.heap[i], self.heap[j] = self.heap[j], self.heap[i]
# 上移操作封装 是个递归
def shift_up(self, i):
if i == 0:
return
pi = self.get_parent_index(i)
if self.heap[pi] > self.heap[i]:
self.swap(pi, i)
self.shift_up(pi)
# 下移操作
def shift_down(self, i):
size = self.size()
li = self.get_left_index(i)
ri = self.get_right_index(i)
cur = self.heap[i]
smallest = i
if li < size and self.heap[li] < cur:
smallest = li
if ri < size and self.heap[ri] < self.heap[smallest]:
smallest = ri
if smallest != i:
self.swap(smallest, i)
self.shift_down(smallest)
# 添加
def insert(self, value):
self.heap.append(value)
self.shift_up(len(self.heap) - 1)
# 删除
def pop(self):
if self.size() == 0:
return
self.heap[0] = self.heap.pop()
self.shift_down(0)
# 获取堆顶
def peak(self):
if self.size() == 0:
return None
return self.heap[0]
# 获取堆大小
def size(self):
return len(self.heap)
# 输出最后的堆结构
def print(self):
print(self.heap)
def main():
# 测试用例
heap = MinHeap()
# 测试插入操作
heap.insert(3)
heap.insert(1)
heap.insert(6)
heap.insert(5)
heap.insert(2)
heap.insert(4)
print("After insertions:")
heap.print() # 应该输出 [1, 2, 4, 5, 3, 6]
# 测试获取堆顶元素
peek = heap.peak()
print("Peek:", peek) # 应该输出 1
# 测试删除堆顶元素
heap.pop()
print("After pop:")
heap.print() # 应该输出 [2, 3, 4, 5, 6]
# 测试再次删除堆顶元素
heap.pop()
print("After another pop:")
heap.print() # 应该输出 [3, 5, 4, 6]
# 测试获取堆的大小
print("Size:", heap.size()) # 应该输出 4
# 测试空堆的情况
empty_heap = MinHeap()
peek_empty = empty_heap.peak()
print("Empty heap peek:", peek_empty) # 应该输出 None
empty_heap.pop()
print("Empty heap size after pop:", empty_heap.size()) # 应该输出 0
if __name__ == "__main__":
main()
Golang 版算法实现
1 ) 方案1
/*
堆和实现最小堆类
*/
package main
import (
"fmt"
)
type MinHeap struct {
heap []int
}
// 获取父节点
func (h *MinHeap) getParentIndex(i int) int {
return (i - 1) >> 1
}
// 获取左子节点
func (h *MinHeap) getLeftIndex(i int) int {
return (i << 1) + 1
}
// 获取右子节点
func (h *MinHeap) getRightIndex(i int) int {
return (i << 1) + 2
}
// 两数交换
func (h *MinHeap) swap(i, j int) {
h.heap[i], h.heap[j] = h.heap[j], h.heap[i]
}
// 上移操作封装 是个递归
func (h *MinHeap) shiftUp(i int) {
if i == 0 {
return
}
pi := h.getParentIndex(i)
if h.heap[pi] > h.heap[i] {
h.swap(pi, i)
h.shiftUp(pi)
}
}
// 下移操作
func (h *MinHeap) shiftDown(i int) {
size := h.size()
li := h.getLeftIndex(i)
ri := h.getRightIndex(i)
cur := h.heap[i]
smallest := i
if li < size && h.heap[li] < cur {
smallest = li
}
if ri < size && h.heap[ri] < h.heap[smallest] {
smallest = ri
}
if smallest != i {
h.swap(smallest, i)
h.shiftDown(smallest)
}
}
// 添加
func (h *MinHeap) insert(value int) {
h.heap = append(h.heap, value)
h.shiftUp(len(h.heap) - 1)
}
// 删除
func (h *MinHeap) pop() {
if h.size() == 0 {
return
}
h.heap[0] = h.heap[len(h.heap)-1]
h.heap = h.heap[:len(h.heap)-1]
h.shiftDown(0)
}
// 获取堆顶
func (h *MinHeap) peak() *int {
if h.size() == 0 {
return nil
}
return &h.heap[0]
}
// 获取堆大小
func (h *MinHeap) size() int {
return len(h.heap)
}
// 输出最后的堆结构
func (h *MinHeap) print() {
fmt.Println(h.heap)
}
func main() {
// 测试用例
heap := &MinHeap{}
// 测试插入操作
heap.insert(3)
heap.insert(1)
heap.insert(6)
heap.insert(5)
heap.insert(2)
heap.insert(4)
fmt.Println("After insertions:")
heap.print() // 应该输出 [1, 2, 4, 5, 3, 6]
// 测试获取堆顶元素
peek := heap.peak()
if peek != nil {
fmt.Println("Peek:", *peek) // 应该输出 1
} else {
fmt.Println("Peek: nil")
}
// 测试删除堆顶元素
heap.pop()
fmt.Println("After pop:")
heap.print() // 应该输出 [2, 3, 4, 5, 6]
// 测试再次删除堆顶元素
heap.pop()
fmt.Println("After another pop:")
heap.print() // 应该输出 [3, 5, 4, 6]
// 测试获取堆的大小
fmt.Println("Size:", heap.size()) // 应该输出 4
// 测试空堆的情况
emptyHeap := &MinHeap{}
peekEmpty := emptyHeap.peak()
if peekEmpty != nil {
fmt.Println("Empty heap peek:", *peekEmpty) // 应该输出 nil
} else {
fmt.Println("Empty heap peek: nil")
}
emptyHeap.pop()
fmt.Println("Empty heap size after pop:", emptyHeap.size()) // 应该输出 0
}
457



被折叠的 条评论
为什么被折叠?



