数据结构:堆

一、堆的定义

  • 堆是一种完全二叉树 结构,同时满足堆的性质,即父节点和子节点之间存在固定的大小关系。它主要用于高效地获取和维护极值(最大值或最小值),常见的堆有大顶堆和小顶堆两种。
  • 资料:https://pan.quark.cn/s/43d906ddfa1bhttps://pan.quark.cn/s/90ad8fba8347https://pan.quark.cn/s/d9d72152d3cf

二、堆的核心性质

  1. 结构性质

    • 堆是一棵完全二叉树,即除了最后一层,其他层的节点数都达到最大值,且最后一层的节点都靠左排列。
    • 通常用数组来存储堆,对于数组中索引为i的节点(从0开始),其左子节点索引为2i+1,右子节点索引为2i+2,父节点索引为(i-1)//2
  2. 数值性质

    • 大顶堆 :每个父节点的值都大于或等于其左右子节点的值,堆顶(根节点)是整个堆的最大值。
    • 小顶堆 :每个父节点的值都小于或等于其左右子节点的值,堆顶(根节点)是整个堆的最小值。

三、堆的核心操作

1. 上浮(Up-Heapify)

当向堆中插入新元素时,新元素会被放在数组末尾,然后需要通过上浮操作调整其位置,以满足堆的性质。

  • 操作逻辑:比较当前节点与其父节点的值,若不满足堆的性质(大顶堆中当前节点值大于父节点,小顶堆中当前节点值小于父节点),则交换两者位置,重复此过程直到满足堆性质或到达堆顶。

2. 下沉(Down-Heapify)

当堆顶元素被移除或堆中元素值被修改时,需要通过下沉操作调整堆的结构,维持堆的性质。

  • 操作逻辑:比较当前节点与其左右子节点的值,选择符合堆性质的子节点(大顶堆选值最大的子节点,小顶堆选值最小的子节点),若当前节点与该子节点不满足堆性质则交换,重复此过程直到满足堆性质或成为叶子节点。

3. 插入元素

  • 步骤1:将新元素添加到数组末尾;
  • 步骤2:对新元素执行上浮操作,调整堆结构。

4. 删除堆顶元素

  • 步骤1:将堆顶元素与数组末尾元素交换;
  • 步骤2:删除数组末尾的原堆顶元素;
  • 步骤3:对新的堆顶元素执行下沉操作,调整堆结构。

5. 构建堆

将一个无序数组转换为堆结构,通常从最后一个非叶子节点开始,从下到上依次执行下沉操作。

  • 最后一个非叶子节点的索引为(n//2)-1n为数组长度)。

四、堆的时间复杂度

  • 插入操作:时间复杂度为O(log n),上浮操作最多遍历堆的高度次节点。
  • 删除堆顶操作:时间复杂度为O(log n),下沉操作最多遍历堆的高度次节点。
  • 构建堆:时间复杂度为O(n),优于逐个插入的O(n log n)
  • 获取堆顶元素:时间复杂度为O(1),可直接访问数组首个元素。

五、堆的实现示例(小顶堆)

python 复制代码
class MinHeap:
    def __init__(self):
        self.heap = []
    
    def parent(self, idx):
        return (idx - 1) // 2
    
    def left_child(self, idx):
        return 2 * idx + 1
    
    def right_child(self, idx):
        return 2 * idx + 2
    
    def swap(self, i, j):
        self.heap[i], self.heap[j] = self.heap[j], self.heap[i]
    
    def up_heapify(self, idx):
        # 上浮操作,维护小顶堆性质
        while idx > 0 and self.heap[idx] < self.heap[self.parent(idx)]:
            self.swap(idx, self.parent(idx))
            idx = self.parent(idx)
    
    def down_heapify(self, idx):
        # 下沉操作,维护小顶堆性质
        n = len(self.heap)
        while True:
            smallest = idx
            left = self.left_child(idx)
            right = self.right_child(idx)
            if left < n and self.heap[left] < self.heap[smallest]:
                smallest = left
            if right < n and self.heap[right] < self.heap[smallest]:
                smallest = right
            if smallest == idx:
                break
            self.swap(idx, smallest)
            idx = smallest
    
    def insert(self, val):
        # 插入元素
        self.heap.append(val)
        self.up_heapify(len(self.heap) - 1)
    
    def extract_min(self):
        # 删除并返回堆顶最小值
        if len(self.heap) == 0:
            return None
        if len(self.heap) == 1:
            return self.heap.pop()
        min_val = self.heap[0]
        self.heap[0] = self.heap.pop()
        self.down_heapify(0)
        return min_val
    
    def get_min(self):
        # 获取堆顶最小值
        return self.heap[0] if self.heap else None
    
    def build_heap(self, arr):
        # 构建小顶堆
        self.heap = arr.copy()
        n = len(self.heap)
        # 从最后一个非叶子节点开始下沉
        for i in range((n // 2) - 1, -1, -1):
            self.down_heapify(i)

使用示例

python 复制代码
heap = MinHeap()
# 插入元素
heap.insert(5)
heap.insert(3)
heap.insert(8)
heap.insert(1)
print(heap.get_min())  # 输出1

# 提取堆顶
print(heap.extract_min())  # 输出1
print(heap.get_min())  # 输出3

# 构建堆
arr = [9, 4, 7, 1, 3, 6]
heap.build_heap(arr)
print(heap.get_min())  # 输出1

六、堆的常见应用

  1. 优先队列:堆是优先队列的底层实现,可根据优先级(极值)快速获取和删除元素,例如任务调度、事件驱动系统。
  2. 堆排序 :利用堆的性质实现排序,时间复杂度为O(n log n),分为构建堆和逐次提取堆顶两个阶段。
  3. Top-K问题 :从海量数据中获取前K大或前K小的元素,通过维护一个大小为K的小顶堆(求Top-K大)或大顶堆(求Top-K小),可在O(n log K)时间内完成。
  4. 中位数维护:用大顶堆存储左半部分数据,小顶堆存储右半部分数据,可快速获取中位数。
  5. 多路归并排序:合并多个有序数组时,用堆快速获取各数组当前最小值,实现高效归并。

七、堆的扩展

  1. 二项堆:支持快速合并两个堆,适用于频繁合并的场景。
  2. 斐波那契堆 :理论上部分操作(如合并、插入)的时间复杂度为O(1),但实现复杂,多用于理论分析。
相关推荐
明月_清风5 小时前
从二叉树到 B+ 树:一文搞懂工程中「树」的演化之道
数据结构·算法·go
渡我白衣5 小时前
并查集:基础认识与模拟实现
android·java·javascript·数据结构·c++·算法·并查集
血小板要健康6 小时前
链表 阶段算法总结
java·数据结构·笔记·算法·leetcode·链表
wuyk55510 小时前
107.FreeRTOS 链表深度解析:从原理到面试满分答案
c语言·开发语言·数据结构·stm32·单片机·链表·面试
-dzk-10 小时前
【链表】LC 138.随机链表的复制
数据结构·链表
洋不写bug11 小时前
链表补充练习,双链表的模拟实现
java·数据结构·链表·双链表·底层实现
白狐_79812 小时前
408 数据结构|外部排序:流程与 k 路归并
数据结构·算法
我不会起名字32214 小时前
一天一道算法题(26):栈的简单应用
java·数据结构·python·算法·leetcode·golang·
不会就选b14 小时前
算法日常・每日刷题--<贪心+大根堆>2
数据结构·算法
事圆则缓14 小时前
Java 常见数据结构与 Android 使用场景
android·java·数据结构