二叉堆是树结构的一种,它满足以下性质:
(1) 堆中任意节点的值总是不大于(不小于)其子节点的值;
(2) 堆总是一棵完全树;
(3) 节点和节点之间应具有某种可比性
将任意节点不小于/不大于其子节点的堆叫做最大堆/最小堆,图示如下:
最小堆:最小值在顶端
最大堆:最大值在顶端
二叉堆索引之间关系
二叉堆中添加元素的思路,最大堆为例(最大值在最上面):
(1)将元素添加到堆最后,并根据其索引计算出父节点的索引;
(2)比较父节点和该节点,该节点大于父节点,则两者交换位置和索引;
(3)之后根据新索引计算出新的父节点索引,再进行下一轮比较,之后依此类推;
二叉堆中取出元素的思路,最大堆为例(最大值在最上面):
(1)二叉堆每次取出元素都只从root节点取值,这样每次取出的值都是该二叉堆中的最大值;
(2)将root节点取出后,用二叉堆中最后一个节点放到root节点原来的位置,之后做下沉操作;
(3)下沉思路:根据父节点索引计算出左右节点索引,再与左右节点对比,如果父节点小于左右节点中的最大值,则和左右节点中最大值交换位置,之后根据新父节点索引计算新左右节点索引,进行下一轮对比,之后依此类推;
(4)左右节点相等且大于父节点的情况下,优先选择和左节点做换位置;
最大堆的定义和实现:
// 最大堆
type maxHeap struct {
size int
nums []int
}
// 获取父节点索引
func parent(i int) int {
if i == 0 {
return 0
}
return (i - 1) / 2
}
// 获取左节点索引
func leftChild(i int) int {
return 2*i + 1
}
func rightChild(i int) int {
return 2*i + 2
}
func NewMaxHeap() *maxHeap {
return &maxHeap{}
}
func (heap *maxHeap) GetSize() int {
return heap.size
}
func (heap *maxHeap) IsEmpty() bool {
return heap.size == 0
}
// 插入元素
func (heap *maxHeap) SiftUp(i int) {
if heap.size < len(heap.nums) {
heap.nums[heap.size] = i
} else { // 扩容
heap.nums = append(heap.nums, i)
}
parI := parent(heap.size)
childI := heap.size
for heap.nums[parI] < heap.nums[childI] {
heap.nums[parI], heap.nums[childI] = heap.nums[childI], heap.nums[parI]
childI = parI
parI = parent(parI)
}
heap.size++
}
// 下沉操作
func siftDown(heap *maxHeap, parI int) {
var maxI int
for {
leftI := leftChild(parI)
switch {
// 左索引超出size
case leftI+1 > heap.size:
return
// 左索引不超,右索引超出size,说明左索引是最后索引
case leftI+2 > heap.size:
if heap.nums[parI] < heap.nums[leftI] {
heap.nums[parI], heap.nums[leftI] = heap.nums[leftI], heap.nums[parI]
}
return
case heap.nums[leftI] >= heap.nums[leftI+1]:
maxI = leftI
case heap.nums[leftI] < heap.nums[leftI+1]:
maxI = leftI + 1
}
// 比左右子节点的值都大,返回
if heap.nums[parI] >= heap.nums[maxI] {
return
}
heap.nums[parI], heap.nums[maxI] = heap.nums[maxI], heap.nums[parI]
parI = maxI
}
}
// 取出对中最大元素,即root节点值
func (heap *maxHeap) SiftDown() (int, error) {
if heap.IsEmpty() {
return 0, errors.New("failed to siftDown,maxHeap is empty.")
}
vTop := heap.nums[0]
heap.size--
heap.nums[0], heap.nums[heap.size] = heap.nums[heap.size], 0
siftDown(heap, 0)
return vTop, nil
}
// 取出最大元素后,再放入一个新元素
func (heap *maxHeap) Replace(i int) (int, error) {
if heap.IsEmpty() {
return 0, errors.New("failed to siftDown,maxHeap is empty.")
}
vTop := heap.nums[0]
heap.nums[0] = i
siftDown(heap, 0)
return vTop, nil
}
// 将数值arrs按照二叉堆的定义排序
func Heapify(arrs *[]int) *[]int {
heap := &maxHeap{
size: len(*arrs),
nums: *arrs,
}
endParI := parent(heap.size - 1)
for endParI >= 0 {
siftDown(heap, endParI)
endParI--
}
return &heap.nums
}
// 打印二叉堆
func (heap *maxHeap) Print() {
str := ""
count := 1
j := 2
k := 1
for i := 0; i < heap.size; i++ {
fmt.Println(str, heap.nums[i])
count++
if count == j {
str = str + "--"
j = 1 << uint(k)
k++
count = 0
}
}
}
// 查看堆中最大元素
func (heap *maxHeap) GetMax() (int, error) {
if heap.IsEmpty() {
return 0, errors.New("failed to get maxVal,maxHeap is empty.")
}
return heap.nums[0], nil
}
测试二叉树
for i := 0; i < 10; i++ {
h.SiftUp(rand.Intn(20))
}
h.Print()
for i := 0; i < 6; i++ {
val, err := h.SiftDown()
fmt.Printf("取出值:%d, 错误:%v\n", val, err)
}
19
-- 16
-- 18
---- 7
---- 1
---- 7
---- 5
------ 0
------ 1
------ 0
取出值:19, 错误:
取出值:18, 错误:
取出值:16, 错误:
取出值:7, 错误:
取出值:7, 错误:
取出值:5, 错误:
用二叉堆解决问题:
1)求n个数字中前m个最大值:https://www.jianshu.com/p/29493ea46f7b
2)求n个数字中前m个高频数字:https://www.jianshu.com/p/57918b843a2d