Skip to main content

堆(Heap)是计算机科学中常用的一种数据结构,它是一种特殊的完全二叉树。在堆中,节点的值总是大于或等于(最大堆)或小于或等于(最小堆)其子节点的值。堆通常用数组来表示,这样可以有效地利用空间并快速访问任何节点。

堆(Heap)这个术语通常指的是二叉堆,但实际上,堆是一个更广泛的概念,包括多种不同的实现。以下是一些常见的堆及其特点:

  1. 二叉堆(Binary Heap)

    • 最常见的堆实现。
    • 是一个完全二叉树,可以是最大堆或最小堆。
    • 插入和删除操作的时间复杂度为 O(log n)。
    • 通常使用数组实现,父节点和子节点之间有简单的索引关系。
  2. 斐波那契堆(Fibonacci Heap)

    • 支持多种操作,其摊还时间复杂度优于二叉堆。
    • 插入和减小键值的摊还时间复杂度为 O(1)。
    • 删除最小元素的摊还时间复杂度为 O(log n)。
    • 适用于包含大量插入和键值减小操作的图算法,如 Dijkstra 和 Prim 算法。
  3. 二项堆(Binomial Heap)

    • 由一组满足二项分布特性的二项树组成。
    • 结构类似于斐波那契堆,但实现简单。
    • 插入、删除和合并操作的时间复杂度为 O(log n)。
  4. 配对堆(Pairing Heap)

    • 是斐波那契堆的简化版本,具有类似的摊还时间复杂度。
    • 插入、合并和减小键值的摊还时间复杂度为 O(1)。
    • 删除最小元素的摊还时间复杂度为 O(log n)。
    • 通常用于实现简单的优先级队列。
  5. 左倾堆(Leftist Heap)

    • 是一种非常规的二叉堆,它保证树的左侧深度至少与右侧一样深。
    • 主要用于高效合并两个堆的操作。
    • 插入、删除和合并操作的时间复杂度为 O(log n)。
  6. 斜堆(Skew Heap)

    • 是左倾堆的变种,也是自调整型的堆。
    • 合并操作的时间复杂度为 O(log n),但是不保证严格的时间复杂度,因为它是自调整的。
  7. d-ary Heap

    • 是二叉堆的扩展,每个节点可以有 d 个子节点,而不仅仅是两个。
    • 这种类型的堆在减少树高度和优化堆操作方面很有用,特别是当堆中的元素非常多时。
  8. 最小-最大堆(Min-Max Heap)

    • 一种可以同时支持快速找到最小值和最大值的数据结构。
    • 对于需要同时快速访问最小和最大元素的应用场景很有用。
  9. 软堆(Soft Heap)

    • 是一种近似的优先级队列,允许一定比例的错误。
    • 用于某些优化算法,其中元素的精确顺序不是非常关键。

每种堆都有其特定的用途和优势。在实际应用中,选择合适的堆结构取决于问题的需求和性能要求。例如,二叉堆因其简单性和易于实现而被广泛使用,而斐波那契堆则在需要多次合并优先队列或者执行大量减小键值操作的场景中表现得更好。

堆的作用:

  1. 维护优先级队列: 堆是实现优先级队列的最佳数据结构。在优先级队列中,元素根据它们的优先级被添加或删除。最大堆用于最大优先级队列,最小堆用于最小优先级队列。

  2. 高效的找到最大值或最小值: 在最大堆中,最大元素总是位于根节点。在最小堆中,最小元素总是位于根节点。因此,访问最大元素或最小元素的时间复杂度是 O(1)。

  3. 堆排序: 堆可以用于实现堆排序算法,这是一种时间复杂度为 O(n log n) 的有效排序算法。通过构建最大堆或最小堆并不断移除根节点到数组的末尾,可以对数组进行排序。

堆的用途:

  1. 排序: 堆排序是一种利用堆的性质进行排序的方法。它可以找到未排序序列中的最大(或最小)元素,并将其移动到序列的末尾。

  2. 优先级队列实现: 在很多算法中,比如 Dijkstra's 算法和 A* 算法,需要使用优先级队列来存储待处理的元素,其中元素的优先级决定了它们的处理顺序。

  3. 选择算法: 堆可以用于快速找到一个集合中的第 k 大(或小)的元素。

  4. 图算法: 在图论中,许多算法(如 Prim's 和 Dijkstra's 算法)使用最小堆来跟踪最小边权重或最短路径。

  5. 数据流中的统计: 堆可以用于处理实时数据流,例如,维护数据流中的中位数或任何其他基于顺序的统计数据。

  6. 系统调度: 在操作系统中,堆用于进程调度,根据进程的优先级来分配CPU时间。

  7. 网络带宽管理: 在网络流量管理中,堆可用于控制和优化数据包的发送顺序。

  8. 多路归并: 当你需要将多个排序的文件或列表合并成一个排序的列表时,堆可以用来有效地进行多路归并操作。

堆的实现通常包括基本操作,如插入(通常称为 "push")、删除最大值或最小值(通常称为 "pop")以及堆化(将一个数组转换成堆的过程)。这些操作的时间复杂度通常是 O(log n),其中 n 是堆中元素的数量,这是因为堆的高度是对数的。掌握堆的使用可以在解决许多算法问题时提供非常有效的解决方案。

排序中的用法

堆在排序中的应用通常指的是堆排序算法(Heap Sort),这是一种利用堆数据结构进行排序的算法。堆排序是一种比较排序算法,可以用来对数组进行升序或降序排列。堆排序算法的基本思想如下:

  1. 建立堆

    • 首先将待排序的数组构造成一个最大堆(用于升序排序)或最小堆(用于降序排序)。这个过程称为堆化(heapify),其目的是确保每个父节点的值都大于或小于其子节点的值。
  2. 排序过程

    • 将堆顶元素(最大或最小元素)与数组的最后一个元素交换,这样最大或最小的元素就被放置到了数组的末尾,即其最终位置。
    • 交换后,数组的大小减一(排除了最后一个元素),然后重新对剩余的数组进行堆化,确保新的堆顶元素是当前未排序部分的最大或最小元素。
    • 重复这个过程,直到所有元素都被放置到其最终位置,数组排序完成。

堆排序的时间复杂度分析:

  • 堆化:建立堆的过程可以通过自底向上的方法完成,时间复杂度为 O(n)。
  • 排序:每次调整堆的时间复杂度为 O(log n),因为需要进行下沉操作维持堆的性质。由于需要对 n-1 个元素进行堆调整,所以排序过程的总时间复杂度为 O(n log n)。

因此,堆排序的总体时间复杂度为 O(n log n)。堆排序的空间复杂度为 O(1),因为它是原地排序,不需要额外的存储空间。

堆排序的特点:

  • 不稳定排序:即相同元素的原始顺序可能会在排序过程中改变。
  • 原地排序:不需要额外的存储空间。
  • 时间复杂度为 O(n log n),且这是其最坏、平均和最好情况下的时间复杂度。

堆排序在实践中不如快速排序和归并排序流行,主要是因为它的常数因子较大,但它在某些特定场景(如需要原地排序且不能承受快速排序的最坏情况时间复杂度)中仍然是一个有用的选项。

java中的堆

PriorityQueue

TreeMap