最小/大堆的实现

第一部分:堆的基本概念

1.1 什么是堆?

  • 完全二叉树

  • 堆序性质:最小堆/最大堆

  • 数组表示法:parent = (i-1)/2, left = 2*i+1, right = 2*i+2

1.2 堆的两种类型

 最小堆:父节点 ≤ 子节点
 最大堆:父节点 ≥ 子节点

1.3 时间复杂度分析

操作 时间复杂度
插入 O(log n)
删除堆顶 O(log n)
获取堆顶 O(1)
建堆 O(n)

第二部分:最小堆的完整实现

2.1 类定义和私有成员

class MinHeap {
private:
    vector<int> heap;  // 存储堆元素的数组
    int size;          // 当前堆的大小
    int capacity;      // 堆的容量
    
    // 辅助函数声明
    int parent(int idx) { return (idx - 1) / 2; }
    int leftChild(int idx) { return 2 * idx + 1; }
    int rightChild(int idx) { return 2 * idx + 2; }
    
    // 核心操作
    void heapifyUp(int idx);    // 从下向上调整
    void heapifyDown(int idx);  // 从上向下调整
    void buildHeap();           // 原地建堆

2.2 核心操作实现

// 从下向上调整(插入时使用)
void MinHeap::heapifyUp(int idx) {
    while (idx > 0 && heap[idx] < heap[parent(idx)]) {
        swap(heap[idx], heap[parent(idx)]);
        idx = parent(idx);
    }
}

// 从上向下调整(删除堆顶时使用)
void MinHeap::heapifyDown(int idx) {
    int smallest = idx;
    int left = leftChild(idx);
    int right = rightChild(idx);
    
    // 找出当前节点和两个子节点中最小的
    if (left < size && heap[left] < heap[smallest]) {
        smallest = left;
    }
    if (right < size && heap[right] < heap[smallest]) {
        smallest = right;
    }
    
    // 如果最小的不是当前节点,交换并继续向下调整
    if (smallest != idx) {
        swap(heap[idx], heap[smallest]);
        heapifyDown(smallest);  // 递归版本
        // heapifyDownIterative(smallest);  // 也可以写迭代版本
    }
}

// 迭代版本(可选,展示不同写法)
void MinHeap::heapifyDownIterative(int idx) {
    while (true) {
        int smallest = idx;
        int left = leftChild(idx);
        int right = rightChild(idx);
        
        if (left < size && heap[left] < heap[smallest]) smallest = left;
        if (right < size && heap[right] < heap[smallest]) smallest = right;
        
        if (smallest == idx) break;
        
        swap(heap[idx], heap[smallest]);
        idx = smallest;
    }
}

2.3 公有接口

public:
    // 构造函数
    MinHeap() : capacity(10), size(0) {
        heap.resize(capacity);
    }
    
    explicit MinHeap(int cap) : capacity(cap), size(0) {
        heap.resize(capacity);
    }
    
    // 从数组构造堆(Floyd建堆法,O(n))
    MinHeap(const vector<int>& arr) {
        heap = arr;
        size = heap.size();
        capacity = size;
        buildHeap();
    }
    
    // 插入元素
    void push(int val) {
        if (size == capacity) {
            capacity *= 2;
            heap.resize(capacity);
        }
        
        heap[size] = val;
        heapifyUp(size);
        size++;
    }
    
    // 删除堆顶元素
    int pop() {
        if (size == 0) {
            throw runtime_error("Heap is empty");
        }
        
        int root = heap[0];
        heap[0] = heap[size - 1];
        size--;
        heapifyDown(0);
        
        return root;
    }
    
    // 获取堆顶元素
    int top() const {
        if (size == 0) throw runtime_error("Heap is empty");
        return heap[0];
    }
    
    // 堆是否为空
    bool empty() const { return size == 0; }
    
    // 当前堆大小
    int getSize() const { return size; }
    
    // 清空堆
    void clear() { size = 0; }

2.4 Floyd建堆算法(优化)

private:
    void buildHeap() {
        // 从最后一个非叶子节点开始,向前逐个调整
        for (int i = size / 2 - 1; i >= 0; i--) {
            heapifyDown(i);
        }
    }

第三部分:HeapifyUp 和 HeapifyDown 功能演示

3.1 HeapifyUp 示例(插入新元素时使用)

场景:向最小堆插入值 2

初始最小堆:

									        3
									       / \
									      5   8
									     / \   \
									    10  15  20

数组:[3, 5, 8, 10, 15, 20]

步骤1:插入新元素 2

									        3
									       / \
									      5   8
									     / \   \
									    10  15  20
									   /
									  2    ← 新元素插入在最后一个位置

数组:[3, 5, 8, 10, 15, 20, 2]

步骤2:HeapifyUp 开始
当前节点:2(索引6),父节点:10(索引3)
比较:2 < 10 ✓,需要交换

交换后:

									        3
									       / \
									      5   8
									     / \   \
									    2   15  20
									   /
									  10

数组:[3, 5, 8, 2, 15, 20, 10]

步骤3:继续向上比较
当前节点:2(索引3),父节点:5(索引1)
比较:2 < 5 ✓,需要交换

交换后:

   									        3
   									       / \
   									      2   8
   									     / \   \
   									    5   15  20
   									   /
   									  10

数组:[3, 2, 8, 5, 15, 20, 10]

步骤4:继续向上比较
当前节点:2(索引1),父节点:3(索引0)
比较:2 < 3 ✓,需要交换

交换后:

   									        2
   									       / \
   									      3   8
   									     / \   \
   									    5   15  20
   									   /
   									  10

数组:[2, 3, 8, 5, 15, 20, 10]

步骤5:到达根节点,停止
最终堆满足最小堆性质:

   									        2
   									       / \
   									      3   8
   									     / \   \
   									    5   15  20
   									   /
   									  10

3.2 HeapifyDown 示例(删除堆顶时使用)

场景:删除最小堆的堆顶元素

初始最小堆:

									        2
									       / \
									      3   8
									     / \   \
									    5   15  20
									   /
									  10

数组:[2, 3, 8, 5, 15, 20, 10]

步骤1:删除堆顶元素 2
用最后一个元素 10 替换堆顶:

									       10  ← 原来在最后的元素
									       / \
									      3   8
									     / \   \
									    5   15  20

数组:[10, 3, 8, 5, 15, 20] (删除了最后一个元素)

步骤2:HeapifyDown 开始
当前节点:10(索引0)
比较三个节点:10、左子节点3、右子节点8
找到最小的是 3

步骤3:交换 10 和 3

交换后:

   									        3
   									       / \
   									      10  8
   									     / \   \
   									    5   15  20

数组:[3, 10, 8, 5, 15, 20]

步骤4:继续向下调整
当前节点:10(索引1)
比较三个节点:10、左子节点5、右子节点15
找到最小的是 5

步骤5:交换 10 和 5

交换后:

   									        3
   									       / \
   									      5   8
   									     / \   \
   									    10  15  20

数组:[3, 5, 8, 10, 15, 20]

步骤6:继续检查
当前节点:10(索引3)
左子节点:不存在(超出数组范围)
右子节点:不存在
到达叶子节点,停止

最终堆:

  									        3
  									       / \
  									      5   8
  									     / \   \
  									    10  15  20

数组:[3, 5, 8, 10, 15, 20] ✓ 满足最小堆性质

记忆tips

HeapifyUp(上浮):

想象一下向水中扔一个乒乓球,如果它比周围的水轻(值小),就会向上浮,直到找到合适的位置。

HeapifyDown(下沉):

想象一下一块石头掉进水里,如果它比周围的水重(值大),就会向下沉,直到找到合适的位置。

posted @ 2025-12-16 20:55  Yokino123  阅读(9)  评论(1)    收藏  举报