【堆】最大堆、最小堆以及GO语言的实现
堆是计算机科学中一种特别的完全二叉树结构,在优先队列、图算法和排序算法中有广泛应用。本文将从概念、原理和实现等方面详细介绍堆这一重要的数据结构。
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 = indexleft = 2 * index + 1right = 2 * index + 2// 找到最大子节点(最大堆)或最小子节点(最小堆)if left < heap.size && heap[left] > heap[largest]: // 最大堆的情况largest = leftif 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语言实现的最大堆:
package mainimport ("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) - 1h.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 := indexsize := len(h.array)for {left := 2*index + 1right := 2*index + 2if 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 = arrfor 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. 最小堆的实现
最小堆与最大堆的实现几乎相同,只需改变比较逻辑:
package mainimport ("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) - 1h.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 := indexsize := len(h.array)for {left := 2*index + 1right := 2*index + 2if 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 = arrfor 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是堆中元素的数量。