堆是计算机科学中一种特别的完全二叉树结构,在优先队列、图算法和排序算法中有广泛应用。本文将从概念、原理和实现等方面详细介绍堆这一重要的数据结构。
1. 堆的基本概念
1.1 什么是堆?
堆(Heap)是一种特殊的完全二叉树,具有以下特性:
- 结构性质:堆是一个完全二叉树,即除了最后一层,其他层的节点数都是最大的,且最后一层的节点都集中在左侧。
- 堆序性质:根据堆的类型(最大堆或最小堆),节点的值满足特定的排序关系。
1.2 堆的类型
堆主要分为两种类型:
-
最大堆(Max Heap):任何节点的值都大于或等于其子节点的值。因此,根节点包含堆中的最大值。
-
最小堆(Min Heap):任何节点的值都小于或等于其子节点的值。因此,根节点包含堆中的最小值。
1.3 堆的应用
- 实现优先队列
- 堆排序算法
- 图算法中的Dijkstra算法、Prim算法
- 中位数和第K大元素问题
- 定时器(Timer)的实现
2. 堆的原理与表示
2.1 数组表示
虽然堆是二叉树结构,但通常使用数组来实现,这样不需要存储指针就能访问节点的父节点和子节点。对于数组中索引为i
的元素:
- 父节点的索引:
(i-1)/2
(整数除法) - 左子节点的索引:
2*i + 1
- 右子节点的索引:
2*i + 2
这种表示方法高效且节省空间,也符合完全二叉树的结构特性。
2.2 最大堆的原理
在最大堆中,每个节点的值都大于或等于其子节点的值。这意味着:
- 根节点是堆中的最大元素
- 任一节点的值都大于或等于其子树中的所有节点值
- 从根到叶子的每条路径都是递减的
例如,以下是一个最大堆:
100
/ \
19 36
/ \ / \
17 3 25 1
/ \
2 7
2.3 最小堆的原理
在最小堆中,每个节点的值都小于或等于其子节点的值。这意味着:
- 根节点是堆中的最小元素
- 任一节点的值都小于或等于其子树中的所有节点值
- 从根到叶子的每条路径都是递增的
例如,以下是一个最小堆:
1
/ \
2 5
/ \ / \
3 4 13 10
/ \
8 9
3. 堆的基本操作
3.1 插入操作(Insert)
将新元素添加到堆中的过程:
- 将新元素添加到堆的末尾(数组的末尾)
- 将新元素向上调整(上浮),直到满足堆性质
上浮(Sift Up)过程:
function siftUp(heap, index):
parent = (index - 1) / 2
// 对于最大堆,如果当前节点大于父节点,则交换
// 对于最小堆,如果当前节点小于父节点,则交换
if heap[parent] < heap[index]: // 最大堆的情况
swap(heap[parent], heap[index])
siftUp(heap, parent)
3.2 删除最大(或最小)元素
堆的设计使得获取并删除最值元素(根节点)变得高效:
- 保存根节点的值(作为返回值)
- 将堆的最后一个元素移到根节点位置
- 将根节点向下调整(下沉),直到满足堆性质
- 返回保存的根节点值
下沉(Sift Down)过程:
function siftDown(heap, index):
largest = index
left = 2 * index + 1
right = 2 * index + 2
// 找到最大子节点(最大堆)或最小子节点(最小堆)
if left < heap.size && heap[left] > heap[largest]: // 最大堆的情况
largest = left
if right < heap.size && heap[right] > heap[largest]: // 最大堆的情况
largest = right
// 如果需要交换
if largest != index:
swap(heap[index], heap[largest])
siftDown(heap, largest)
3.3 堆化(Heapify)
将一个无序数组转换为堆的过程称为堆化。有两种主要方法:
-
自底向上堆化:从最后一个非叶子节点开始,对每个节点执行下沉操作,直到根节点。
function buildHeap(array): for i = array.size/2 - 1 to 0: siftDown(array, i)
-
自顶向下堆化:从空堆开始,逐个插入元素并执行上浮操作。
自底向上的堆化方法更高效,时间复杂度为O(n),而不是自顶向下的O(n log n)。
4. 最大堆的实现
下面是一个用Go语言实现的最大堆:
go
package main
import (
"fmt"
)
// MaxHeap 表示最大堆数据结构
type MaxHeap struct {
array []int
}
// 插入元素
func (h *MaxHeap) Insert(key int) {
h.array = append(h.array, key)
h.siftUp(len(h.array) - 1)
}
// 上浮操作
func (h *MaxHeap) siftUp(index int) {
for parent := (index - 1) / 2; index > 0 && h.array[parent] < h.array[index]; index, parent = parent, (parent-1)/2 {
h.array[index], h.array[parent] = h.array[parent], h.array[index]
}
}
// 获取并删除最大元素
func (h *MaxHeap) ExtractMax() (int, error) {
if len(h.array) == 0 {
return 0, fmt.Errorf("heap is empty")
}
max := h.array[0]
// 将最后一个元素放到根节点
lastIndex := len(h.array) - 1
h.array[0] = h.array[lastIndex]
h.array = h.array[:lastIndex]
// 下沉操作
if len(h.array) > 0 {
h.siftDown(0)
}
return max, nil
}
// 下沉操作
func (h *MaxHeap) siftDown(index int) {
maxIndex := index
size := len(h.array)
for {
left := 2*index + 1
right := 2*index + 2
if left < size && h.array[left] > h.array[maxIndex] {
maxIndex = left
}
if right < size && h.array[right] > h.array[maxIndex] {
maxIndex = right
}
if maxIndex == index {
return
}
h.array[index], h.array[maxIndex] = h.array[maxIndex], h.array[index]
index = maxIndex
}
}
// 从数组构建堆
func (h *MaxHeap) BuildHeap(arr []int) {
h.array = arr
for i := len(h.array)/2 - 1; i >= 0; i-- {
h.siftDown(i)
}
}
func main() {
h := &MaxHeap{}
h.BuildHeap([]int{4, 10, 3, 5, 1})
fmt.Println("Max Heap:")
fmt.Println(h.array)
max, _ := h.ExtractMax()
fmt.Println("Extracted max:", max)
fmt.Println("Heap after extraction:", h.array)
h.Insert(15)
fmt.Println("Heap after insertion:", h.array)
}
5. 最小堆的实现
最小堆与最大堆的实现几乎相同,只需改变比较逻辑:
go
package main
import (
"fmt"
)
// MinHeap 表示最小堆数据结构
type MinHeap struct {
array []int
}
// 插入元素
func (h *MinHeap) Insert(key int) {
h.array = append(h.array, key)
h.siftUp(len(h.array) - 1)
}
// 上浮操作
func (h *MinHeap) siftUp(index int) {
for parent := (index - 1) / 2; index > 0 && h.array[parent] > h.array[index]; index, parent = parent, (parent-1)/2 {
h.array[index], h.array[parent] = h.array[parent], h.array[index]
}
}
// 获取并删除最小元素
func (h *MinHeap) ExtractMin() (int, error) {
if len(h.array) == 0 {
return 0, fmt.Errorf("heap is empty")
}
min := h.array[0]
// 将最后一个元素放到根节点
lastIndex := len(h.array) - 1
h.array[0] = h.array[lastIndex]
h.array = h.array[:lastIndex]
// 下沉操作
if len(h.array) > 0 {
h.siftDown(0)
}
return min, nil
}
// 下沉操作
func (h *MinHeap) siftDown(index int) {
minIndex := index
size := len(h.array)
for {
left := 2*index + 1
right := 2*index + 2
if left < size && h.array[left] < h.array[minIndex] {
minIndex = left
}
if right < size && h.array[right] < h.array[minIndex] {
minIndex = right
}
if minIndex == index {
return
}
h.array[index], h.array[minIndex] = h.array[minIndex], h.array[index]
index = minIndex
}
}
// 从数组构建堆
func (h *MinHeap) BuildHeap(arr []int) {
h.array = arr
for i := len(h.array)/2 - 1; i >= 0; i-- {
h.siftDown(i)
}
}
func main() {
h := &MinHeap{}
h.BuildHeap([]int{4, 10, 3, 5, 1})
fmt.Println("Min Heap:")
fmt.Println(h.array)
min, _ := h.ExtractMin()
fmt.Println("Extracted min:", min)
fmt.Println("Heap after extraction:", h.array)
h.Insert(0)
fmt.Println("Heap after insertion:", h.array)
}
6. 堆的性能分析
6.1 时间复杂度
操作 | 时间复杂度 |
---|---|
插入元素 | O(log n) |
删除最大/最小元素 | O(log n) |
获取最大/最小元素 | O(1) |
构建堆 | O(n) |
6.2 空间复杂度
堆的空间复杂度为O(n),其中n是堆中元素的数量。