背景
最近在解决算法问题时用到了优先队列,然后发现对其底层实现的一些细节好像没那么熟悉了,于是乎梳理一下。
算法思维
理解堆排序的核心是堆的定义是什么, 通常堆分为大顶堆,小顶堆。这里我以大顶堆为例聊一下堆排序。
大顶堆需要满足一下性质:
- 是一棵完全二叉树; 这个性质使得我们可以用数组来对树结构进行描述,可以通常数组下标之间的关系表达树节点的父子关系。
- 对于这棵树的任意子树,父节点的值都大于子节点的值;这是一种递归的定义,从这条性质我们可以得出堆是一棵满足特定条件的完全二叉树
接下来,对于堆对外提供的所有操作,我们只需要保证不破坏堆的这两条性质就好。
举例来说:无序数组{3,1,4,6,8,9,1,3,2}
其对应的树如下:
所谓树化操作就是将原树改造为符合如下性质的树(大顶堆)。 改造过程比较简单,可以递归的取做这个事情,
- 左右子树分别改造为堆
- 自顶向下调整树节点,使得整棵树符合堆的特性
这里需要注意的是,左右子树为堆之后不能仅仅简单的只交换父子节点使得整棵树为大顶堆,因为交换的过程可能会破坏已有的堆。
插入/删除:
插入/删除操作都有可能破坏以上两条性质,插入时需要调整叶子节点到根节点这条路径,以保证满足堆的性质。这里的删除只能删除根节点,且算法比较巧妙,交换根节点和最后一个节点,新的根节点可能会破坏堆的性质,此时需要再次自顶向下调整。
代码实现
package algo
import (
"errors"
"fmt"
)
/**
* 实现简单的堆排序,这里实现简单的大顶堆
* 用数组下标之间的关系描述父子节点关系
*/
type Heap struct {
Arr []int //用数组描述堆
}
/**
* 建立堆
*/
func (h *Heap) BuildHeap(arr []int) {
h.Arr = arr
h.recurBuildHeap(0)
}
func (h *Heap) recurBuildHeap(root int) {
if root >= len(h.Arr) {
return
}
//叶子节点则直接返回
if 2*root+1 >= len(h.Arr) {
return
}
//左右树化
h.recurBuildHeap(2*root + 1)
h.recurBuildHeap(2*root + 2)
h.Heapify(root)
return
}
/**
* 将以root为根节点的子树堆化
* 这里的前提是root的左右子树已经满足最大堆的性质了
*/
func (h *Heap) Heapify(root int) {
if root < 0 || root >= len(h.Arr) {
return
}
maxIndex := root
leftChild, rightChild := 2*root+1, 2*root+2
if leftChild < len(h.Arr) && h.Arr[leftChild] > h.Arr[maxIndex] {
maxIndex = leftChild
}
if rightChild < len(h.Arr) && h.Arr[rightChild] > h.Arr[maxIndex] {
maxIndex = rightChild
}
h.Arr[maxIndex], h.Arr[root] = h.Arr[root], h.Arr[maxIndex]
if maxIndex != root {
h.Heapify(maxIndex)
}
}
/**
* 新插入一个节点之后,新的节点为叶子节点,可能会破坏该叶子节点所属最小子树的堆特性
* 因此自底向上堆排序
*/
func (h *Heap) Insert(val int) {
h.Arr = append(h.Arr, val)
cur := len(h.Arr) - 1
for cur > 0 { //cur==0时为根节点 没有父节点
parent := (cur - 1) / 2
if h.Arr[parent] < h.Arr[cur] {
h.Arr[parent], h.Arr[cur] = h.Arr[cur], h.Arr[parent]
cur = parent
} else {
break
}
}
return
}
/**
* 取堆最值
*/
func (h *Heap) Pop() (val int, err error) {
if len(h.Arr) == 0 {
err = errors.New("empty heap")
return
}
val = h.Arr[0]
h.Arr[0], h.Arr[len(h.Arr)-1] = h.Arr[len(h.Arr)-1], h.Arr[0]
h.Arr = h.Arr[:len(h.Arr)-1]
h.Heapify(0)
return
}
func Demo() {
arr := []int{3, 1, 5, 1, 2, 3, 6, 8, 132, 45, 1, 2}
h := &Heap{}
h.BuildHeap(arr)
for i := 0; i < len(arr); i++ {
val, _ := h.Pop()
fmt.Printf("%d ", val)
}
fmt.Println()
//
h.BuildHeap(arr)
h.Insert(12333)
for i := 0; i < len(arr); i++ {
val, _ := h.Pop()
fmt.Printf("%d ", val)
}
fmt.Println()
}