【堆】最大堆、最小堆以及GO语言的实现

堆是计算机科学中一种特别的完全二叉树结构,在优先队列、图算法和排序算法中有广泛应用。本文将从概念、原理和实现等方面详细介绍堆这一重要的数据结构。

1. 堆的基本概念

1.1 什么是堆?

堆(Heap)是一种特殊的完全二叉树,具有以下特性:

  • 结构性质:堆是一个完全二叉树,即除了最后一层,其他层的节点数都是最大的,且最后一层的节点都集中在左侧。
  • 堆序性质:根据堆的类型(最大堆或最小堆),节点的值满足特定的排序关系。

1.2 堆的类型

堆主要分为两种类型:

  1. 最大堆(Max Heap):任何节点的值都大于或等于其子节点的值。因此,根节点包含堆中的最大值。

  2. 最小堆(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)

将新元素添加到堆中的过程:

  1. 将新元素添加到堆的末尾(数组的末尾)
  2. 将新元素向上调整(上浮),直到满足堆性质
上浮(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 删除最大(或最小)元素

堆的设计使得获取并删除最值元素(根节点)变得高效:

  1. 保存根节点的值(作为返回值)
  2. 将堆的最后一个元素移到根节点位置
  3. 将根节点向下调整(下沉),直到满足堆性质
  4. 返回保存的根节点值
下沉(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)

将一个无序数组转换为堆的过程称为堆化。有两种主要方法:

  1. 自底向上堆化:从最后一个非叶子节点开始,对每个节点执行下沉操作,直到根节点。

    复制代码
    function buildHeap(array):
        for i = array.size/2 - 1 to 0:
            siftDown(array, i)
  2. 自顶向下堆化:从空堆开始,逐个插入元素并执行上浮操作。

自底向上的堆化方法更高效,时间复杂度为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是堆中元素的数量。

相关推荐
蒟蒻小袁4 分钟前
力扣面试150题--二叉树的最大深度
算法·leetcode·面试
bj328121 分钟前
树的同构问题--Python
开发语言·python·算法
八股文领域大手子33 分钟前
单机 vs 分布式:Java 后端限流的选择题
java·开发语言·数据结构·算法·spring
keep intensify2 小时前
【数据结构】--- 双向链表的增删查改
c语言·数据结构·算法·链表
zl_dfq3 小时前
C语言 之 【栈的简介、栈的实现(初始化、销毁、入栈、出栈、判空、栈的大小、访问栈顶元素、打印)】
c语言·数据结构
geneculture3 小时前
融智学数学符号体系的系统解读(之一)
人工智能·算法·机器学习
巷9554 小时前
DBSCAN对比K-means
算法·机器学习·kmeans
江沉晚呤时4 小时前
深入解析 SqlSugar 与泛型封装:实现通用数据访问层
数据结构·数据库·oracle·排序算法·.netcore
DeyouKong4 小时前
Go反射-通过反射调用结构体的方法(带入参)
开发语言·ios·golang