C++ 堆 的基础与 二叉堆详解

C++ 的基础与 二叉堆(Binary Heap)

"堆" 在 C++ 里既是 动态内存分配池 ,也是一种 数据结构。下面先简要说明两者的区别,然后从头到尾讲解二叉堆的实现与操作,最后给出完整可运行的 C++ 代码示例。


1. 先说"堆"到底是什么?

语境 说明 关键点
C++ 语义 new/deletemalloc/free 所使用的 动态内存池 也叫 heap memory ,和栈(stack)相对;由操作系统管理
数据结构 一种 堆(Heap) ,比如 堆排序优先队列 与内存池无关;采用数组或链表实现

本文聚焦 数据结构的堆 ------ 二叉堆(Binary Heap)


2. 二叉堆(Binary Heap)概念

2.1 什么是二叉堆?

二叉堆 (Heap)的最常见实现形式,是一种 满足完全二叉树 + 堆序性质 的数据结构。

  • 完全二叉树:除最后一层外,每层节点数都达到最大,且最后一层节点左对齐。
  • 堆序性质
    • 最小堆(Min-Heap):父节点 ≤ 所有子节点。
    • 最大堆(Max-Heap):父节点 ≥ 所有子节点。

于是,二叉堆既是 ,又是数组(后面会讲解为什么用数组实现)。

2.2 二叉堆的基本属性

属性 说明
节点数 n
高度 ⌈log₂n⌉(复杂度分析时的常用量)
父子关系(数组实现) 取 0‑based: parent(i) = (i-1)/2left(i) = 2i+1right(i) = 2i+2

3. 用数组实现二叉堆

3.1 为什么用数组?

场景 说明
空间占用 只需要一段连续内存,节省指针开销(树指针占 8 bytes × 节点)
指针运算 计算父子索引简单、常数时间
Cache友好 连续内存访问使 CPU 缓存命中率高

注意:数组实现不需要额外维护节点对象,只存值即可。

3.2 ASCII 图表示(最简 7 节点最小堆)

复制代码
          4                      4
        /   \                  /   \
      10     12               10     12
     / \    /
    30 11  20               30 11  20

支持快速划分:
父节点 | 右子节点 | 左子节点
parent = (idx-1)/2
left = 2*idx+1
right = 2*idx+2


4. 主要操作

操作 完成步骤 复杂度
Insert ① 放到数组末尾 → ② 上滤(SiftUp)直到堆序 O(log n)
Pop(取/删除根) ① 把根元素保存 → ② 用数组最后一个元素填根 → ③ 下滤(SiftDown) O(log n)
Peek 直接返回 heap[0] O(1)
Build (building from scratch) ① 直接复制数据到数组 → ② 从最后一个非叶子节点往上调用 SiftDown O(n)
HeapSort ① 用 Build 生成堆 → ② 每次弹出根(交换头尾元素) O(n log n)

下面展开 SiftUpSiftDown 的细节。

4.1 上滤(Sift-Up,插入)

使用最小堆为例

复制代码
Insert(MinHeap, value):
    1. Append value at the end of array (heap).
    2. idx = size-1
    3. while idx > 0:
          parent = (idx-1)/2
          if heap[parent] <= heap[idx]:
              break          // 维持堆序
          swap(heap[parent], heap[idx])
          idx = parent

上滤的核心是 不断跟父节点比较、必要时交换,直到找到正确的位置。

4.2 下滤(Sift-Down,取/删除根)

复制代码
SiftDown(MinHeap, idx):
    n = heap.size()
    while true:
        left = 2*idx + 1
        right = 2*idx + 2
        smallest = idx

        if left < n && heap[left] < heap[smallest]:
            smallest = left
        if right < n && heap[right] < heap[smallest]:
            smallest = right

        if smallest == idx:
            break

        swap(heap[smallest], heap[idx])
        idx = smallest

下滤从根向下选择最小子节点与当前节点交换,保持堆序。

4.3 建堆(Build-Heap)

复制代码
BuildHeap(array A):
    n = A.size()
    heap = A          // 直接用传入数组,省去复制
    for i = (n-2)/2 downto 0: // 从最后一个非叶子节点开始
        SiftDown(heap, i)

时间复杂度是 O(n),细节见这里


5. C++ 代码实现

下面给出完整可编译最小堆 实现,支持 push, top, pop, size, empty, 与 STL std::priority_queue 对比。

cpp 复制代码
#include <iostream>
#include <vector>
#include <stdexcept>

/* ----------- MinHeap ---------------- */
template<typename T>
class MinHeap {
public:
    // 默认构造
    MinHeap() = default;
    // 用已有数据构建堆
    explicit MinHeap(const std::vector<T>& data) : heap_(data) {
        buildHeap();
    }

    // 获取堆顶(最小值)
    const T& top() const {
        if (heap_.empty()) throw std::out_of_range("Heap is empty");
        return heap_[0];
    }

    // 插入
    void push(const T& value) {
        heap_.push_back(value);
        siftUp(heap_.size() - 1);
    }

    // 弹出堆顶
 void pop() {
        if (heap_.empty()) throw std::out_of_range("Heap is empty");
        if (heap_.size() == 1) {
            heap_.pop_back();
            return;
        }
        std::swap(heap_[0], heap_.back());
        heap_.pop_back();
        siftDown(0);
    }

    // 取堆大小
    std::size_t size() const noexcept { return heap_.size(); }
    bool empty() const noexcept { return heap_.empty(); }

private:
    std::vector<T> heap_;

    // 上滤
    void siftUp(std::size_t idx) {
        while (idx > 0) {
            std::size_t parent = (idx - 1) / 2;
            if (heap_[parent] <= heap_[idx]) break;
            std::swap(heap_[parent], heap_[idx]);
            idx = parent;
        }
    }

    // 下滤
    void siftDown(std::size_t idx) {
        std::size_t n = heap_.size();
        while (true) {
            std::size_t left = 2 * idx + 1;
            std::size_t right = 2 * idx + 2;
            std::size_t smallest = idx;

            if (left < n && heap_[left] < heap_[smallest]) 
                smallest = left;
            if (right < n && heap_[right] < heap_[smallest]) 
                smallest = right;

            if (smallest == idx) break;
            std::swap(heap_[smallest], heap_[idx]);
            idx = smallest;
        }
    }

    // 建堆
    void buildHeap() {
        if (heap_.empty()) return;
        for (std::size_t i = (heap_.size()-2)/2; i != static_cast<std::size_t>(-1); --i) {
            siftDown(i);
        }
    }
};

/* --------- 例子:堆排序 ------------- */
template<typename T>
void heapSort(std::vector<T>& v) {
    MinHeap<T> h(v);           // 先构造堆
    for (std::size_t i = 0; i < v.size(); ++i) {
        v[i] = h.top();        // 取堆顶,每次弹出
        h.pop();
    }
}

/* --------- 主程序展示 ---------- */
int main() {
    std::vector<int> data = {30, 10, 12, 4, 20, 11};

    std::cout << "原始数组: ";
    for (int x : data) std::cout << x << ' ';
    std::cout << "\n";

    MinHeap<int> heap(data);   // 构建堆
    std::cout << "堆顶: " << heap.top() << "\n";  // 最小值

    std::cout << "插入 2\n";
    heap.push(2);
    std::cout << "现在堆顶: " << heap.top() << "\n";

    std::cout << "弹出堆顶\n";
    heap.pop();
    std::cout << "现在堆顶: " << heap.top() << "\n";

    std::cout << "堆排序后结果: ";
    heapSort(data);
    for (int x : data) std::cout << x << ' ';
    std::cout << "\n";

    return 0;
}

说明

  1. MinHeap<int> heap(data); 直接把已有数组用于构造堆(构造函数内部调用 buildHeap())。
  2. heapSort 先用 MinHeap 构造堆,再一次 pop() 取出并覆盖原数组 ------ 就是 堆排序
  3. 代码兼容 大多数C++标准(C++11 之后)。

6. 典型使用场景

需求 推荐实现 说明
实时优先级调度 std::priority_queue 或自定义 MinHeap 需要 O(log n) 插入/弹出
批量排序 HeapSortMinHeapMaxHeap O(n log n),无需额外空间(除数组)
求 k 大/小 k 大/小堆维护 只保留 k 个元素,插入/弹出 O(log k)
优化性能 用自定义小数组实现堆 减少堆顶访问开销

7. 与 std::priority_queue 对比

std::priority_queue 自定义 MinHeap
底层容器 默认是 std::vector std::vector
比较器 默认最大堆(std::less<T> 自定义,支持最小堆
API push, top, pop, size, empty 与标准一致
可扩展性 通过模板参数可自定义 comparator 可以直接修改 siftUp/siftDown 逻辑

由于 std::priority_queue 默认是 最大堆 ,若想做 最小堆,需要写一个比较器:

复制代码

std::priority_queue<int, std::vector<int>, std::greater<int>> minPQ;


8. 小结

  • 堆(Heap) 是一种 满足堆序的完全二叉树
  • 二叉堆数组 直接实现,节省空间和时间,可通过 parent, left, right 快速计算关系。
  • 主要操作有 Insert(Sift‑Up)Pop(Sift‑Down)Build‑Heap,时间复杂度 O(log⁡n)O(logn) 或 O(n)O(n)。
  • C++ 里既有 STL priority_queue (包装好的二叉堆),也可以自己实现自定义 MinHeapMaxHeap
  • 二叉堆适用于 优先级调度堆排序top‑k 等多种场景。

通过上述代码与示例,你可以在生产环境中直接使用自定义二叉堆,也可以轻松理解 STL 提供的 priority_queue。祝编码愉快 🚀!

相关推荐
Ulyanov2 小时前
《PySide6 GUI开发指南:QML核心与实践》 第十篇:综合实战——构建完整的跨平台个人管理应用
开发语言·python·qt·ui·交互·qml·雷达电子战系统仿真
ian4u2 小时前
车载 Android C++ 完整技能路线:从基础到进阶
android·开发语言·c++
lly2024062 小时前
JSP 过滤器
开发语言
郝学胜-神的一滴2 小时前
[力扣 227] 双栈妙解表达式计算:从思维逻辑到C++实战,吃透反向波兰式底层原理
java·前端·数据结构·c++·算法
aq55356002 小时前
数字资源分发的技术革命与未来趋势
java·开发语言·python·php
AI玫瑰助手2 小时前
Python基础:元组的定义与不可变特性(对比列表)
开发语言·python·信息可视化
张驰咨询公司2 小时前
六西格玛数据分析实战:用Python实现DPMO与西格玛水平计算
开发语言·python·数据分析·六西格玛培训·六西格玛培训公司
invicinble2 小时前
对于java基础
java·开发语言
逻辑驱动的ken2 小时前
Java高频面试考点场景题13
java·开发语言·jvm·面试·求职招聘·春招