数据结构堆
堆是什么
堆是一种数据结构,它需要同时满足两个条件:
- 是一个完全二叉树
完全二叉树首先是一种二叉树结构,每一个节点最多有两个子节点:左子节点和右子节点
每一层节点,从左到右,依次排列
只有当前层节点都添加满了之后,才能继续添加下一层节点
- 所有父节点的值 >= 子节点的值,一般称之为大顶堆;或所有父节点的值 <= 子节点的值,一般称之为小顶堆
怎么样来表示堆
我们从根节点开始,依次给每一个节点添加编号0,1,2,3,4,此时可以用一个一维数组来表示堆
使用一维数组来表示堆后,还可以通过数组下标,来快速定位指定节点的父节点,左子节点和右子节点
假设指定节点的下标为i,则可以通过以下公式快速定位:
- 父节点下标 = (i - 1)/ 2
- 左子节点下标 = 2 * i + 1
- 右子节点下标 = 2 * i + 2
假设当前节点的下标为1
父节点下标 = (1 - 1) / 2 = 0
左子节点下标 = 2 * 1 + 1 = 3
右子节点下标 = 2 * 1 + 2 = 4
如何用一个未排序数组构建一个堆
主要分为两个步骤:
- 挑选节点
- 进行heapify操作
当我们拿到一个未排序的数组时,可以通过这个数组来构建一个完全二叉树
此时满足了堆的第一个条件,但不满足第二个条件
所有父节点的值 >= 子节点的值,一般称之为大顶堆;或所有父节点的值 <= 子节点的值,一般称之为小顶堆
为了满足第二个条件,我们需要对不满足条件的节点的值进行交换操作,这个操作一般称之为heapify
heapify代码:
go
package test
import (
"fmt"
"testing"
)
// arr: 数组
// i: 需要进行heapify操作的节点
// arrLen: 数组长度
func heapify(arr []int, i int, arrLen int) {
// 假设最大值下标为i
max := i
// i节点的左子节点和右子节点
left := 2*i + 1
right := 2*i + 2
// 左子节点下标未越界 && 左子节点值 > 最大值
if left < arrLen && arr[left] > arr[max] {
// 最大值下标变为左子节点下标
max = left
}
// 右子节点下标未越界 && 右子节点值 > 最大值
if right < arrLen && arr[right] > arr[max] {
// 最大值下标变为右子节点下标
max = right
}
// 最大值下标不为i
if max != i {
// 交换节点的值
swap(arr, max, i)
// 因为max节点的值已改变,递归对下标max进行heapify,保证堆结构
heapify(arr, max, arrLen)
}
}
func swap(arr []int, i, j int) {
temp := arr[i]
arr[i] = arr[j]
arr[j] = temp
}
// 测试代码
func TestHeapify(t *testing.T) {
arr := []int{5, 7, 8, 9, 4}
heapify(arr, 1, 5)
fmt.Println(arr)
}
heapify可以保证一个节点满足堆结构,我们只要循环对目标节点进行heapify操作,就可以使完全二叉树成为堆结构,所以我们的下一步是解决如何挑选节点的问题
从哪一个节点开始进行heapify操作:从最后一个节点的父节点开始,依次向前,循环进行heapify操作
构建堆代码:
go
func buildHeap(arr []int) {
// 获取数组长度
arrLen := len(arr)
// 最后一个元素下标
last := arrLen - 1
// 从最后一个节点的父节点开始,依次向前,循环进行heapify操作
for i := (last - 1) / 2; i >= 0; i-- {
heapify(arr, i, arrLen)
}
}
堆排序
如何使用数据结构堆来进行排序
上面我们学习了如何用一个未排序数组来构建一个堆,当我们构建一个大顶堆或小顶堆后,我们可以得到一个数组内最大的值或最小的值
大顶堆的根节点就是数组内最大的值,此时我们交换根节点和最后一个节点,就可以把最大值放到最后一个节点中,也就是数组的最后一个元素
绿色元素代表已排序的元素,此时我们无需关心这些已排序的元素,只用看还未排序的元素即可
此时,由于我们交换了根节点和最后一个节点的值,为了满足堆结构,还需要对交换后的根节点进行heapify操作
进行完heapify操作后,我们又可以循环进行以上操作,交换,heapify操作。。。
完整堆排序
完整堆排序步骤:
- 构建堆
- 交换根节点和未排序的最后一个节点
- 对根节点进行heapify
- 数组长度-1,循环进行步骤2,步骤3
完整堆排序代码:
go
package test
import (
"fmt"
"testing"
)
// 堆排序
func heapSort(arr []int) {
// 构建堆
buildHeap(arr)
// 循环减少数组长度
for i := len(arr) - 1; i > 0; i-- {
// 交换根节点和未排序的最后一个节点
swap(arr, 0, i)
// 对根节点进行heapify
heapify(arr, 0, i)
}
}
func buildHeap(arr []int) {
// 获取数组长度
arrLen := len(arr)
// 最后一个元素下标
last := arrLen - 1
// 从最后一个节点的父节点开始,依次向前,循环进行heapify操作
for i := (last - 1) / 2; i >= 0; i-- {
heapify(arr, i, arrLen)
}
}
// arr: 数组
// i: 需要进行heapify操作的节点
// arrLen: 数组长度
func heapify(arr []int, i int, arrLen int) {
// 假设最大值下标为i
max := i
// i节点的左子节点和右子节点
left := 2*i + 1
right := 2*i + 2
// 左子节点下标未越界 && 左子节点值 > 最大值
if left < arrLen && arr[left] > arr[max] {
// 最大值下标变为左子节点下标
max = left
}
// 右子节点下标未越界 && 右子节点值 > 最大值
if right < arrLen && arr[right] > arr[max] {
// 最大值下标变为右子节点下标
max = right
}
// 最大值下标不为i
if max != i {
// 交换节点的值
swap(arr, max, i)
// 因为max节点的值已改变,递归对下标max进行heapify,保证堆结构
heapify(arr, max, arrLen)
}
}
func swap(arr []int, i, j int) {
temp := arr[i]
arr[i] = arr[j]
arr[j] = temp
}
// 测试代码
func TestHeapify(t *testing.T) {
arr := []int{5, 7, 8, 9, 4}
heapSort(arr)
fmt.Println(arr)
}