NeFut Logo NeFut
Admin Login

In-Depth Analysis of Binary Heap Structure and Operations

Published at: 2026-05-27 09:17 Last updated: 2026-06-10 09:16
#algorithm #C++ #Heap

Core Logic and Mathematical Principles

A binary heap is essentially a complete binary tree that satisfies specific heap properties. Its core logic utilizes the contiguous memory space of an array, implicitly maintaining the tree topology through arithmetic relationships of indices, thereby eliminating pointer overhead.

For a complete binary tree with a root node indexed at $1$, the topological geometric relationships of any node $i$ are as follows:

Taking a max-heap as an example, its mathematical total order relationship must strictly satisfy:

$$\forall i > 1, \quad \text{heap}[\text{parent}(i)] \ge \text{heap}[i]$$

The dynamic maintenance operations of the binary heap are based on two underlying heapification mechanisms, both with a time complexity of $O(\log N)$:

  1. Shift Up: When inserting a new element at the bottom of the heap, if this element violates the heap property, it is swapped up the parent chain until the total order relationship is satisfied.
  2. Shift Down: When popping the highest quality element from the top of the heap, the last element from the bottom of the heap is moved to the top, and then this element is swapped down the path of the larger of its two children until the subtree is re-established.

State Design and Algorithm Derivation

The state of a handwritten binary heap is minimal, requiring only a one-dimensional static array heap[] and a counter sz to record the current number of elements.

1. Push Operation (Insertion) and Shift Up Derivation

The new element is placed at heap[++sz]. Let the current index being examined be $curr$:

2. Pop Operation (Deletion) and Shift Down Derivation

Release the top of the heap by executing heap[1] = heap[sz--]. Let the current sinking index be $curr$:


C++ Standard Source Code

#include <iostream>
#include <vector>
#include <algorithm>

// Strictly limit the space size to prevent constant degradation from dynamic memory allocation
const int MAXN = 100005;

struct MaxHeap {
    int heap[MAXN];
    int sz;

    // Explicit initialization, do not rely on default implicit constructors
    MaxHeap() : sz(0) {}

    // Shift Up: Maintain the heap when a new element is added
    void push(int val) {
        heap[++sz] = val;
        int curr = sz;
        // Critical pitfall: must check curr > 1 before accessing the parent node, otherwise when curr=1, 1/2 becomes 0 and will trigger an out-of-bounds error
        while (curr > 1 && heap[curr] > heap[curr / 2]) {
            std::swap(heap[curr], heap[curr / 2]);
            curr /= 2;
        }
    }

    // Return the top element of the heap
    int top() const {
        return heap[1];
    }

    // Shift Down: Maintain the heap when popping the top
    void pop() {
        if (sz == 0) return;
        heap[1] = heap[sz--];
        int curr = 1;
        while ((curr * 2) <= sz) {
            int t = curr * 2; // Default points to the left child
            // Critical pitfall: Right child boundary check t + 1 <= sz must be written first, otherwise it will lead to out-of-bounds access of non-heap data
            if (t + 1 <= sz && heap[t + 1] > heap[t]) {
                t++; // Lock in the absolute winner among the left and right children
            }
            if (heap[curr] >= heap[t]) {
                break; // Heap property has been restored, forcibly cut off the sinking branch
            }
            std::swap(heap[curr], heap[t]);
            curr = t; // Advance the iteration state
        }
    }

    bool empty() const {
        return sz == 0;
    }
};

int main() {
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(nullptr);

    int n;
    if (std::cin >> n) {
        MaxHeap my_heap;
        for (int i = 0; i < n; ++i) {
            int val;
            std::cin >> val;
            my_heap.push(val);
        }

        while (!my_heap.empty()) {
            std::cout << my_heap.top() << " ";
            my_heap.pop();
        }
        std::cout << "\n";
    }

    return 0;
}

NOIP Practical Pitfall Guide

  1. Building the heap with array indices starting from 0 leads to calculation collapse Some contestants habitually set the heap top at heap[0]. In this case, the left child calculation formula becomes $2i+1$, the right child becomes $2i+2$, and the parent becomes $\frac{i-1}{2}$. When retrieving the parent node, if $i=0$, $\frac{0-1}{2}$ under C++'s rounding towards zero rule will still be $0$, directly triggering a while (heap[curr] > heap[(curr-1)/2]) infinite loop. Therefore, handwritten heaps must strictly store starting from index 1.
  2. Shift Down left and right child selection logic short circuit When shifting down, contestants often write if (heap[curr*2+1] > heap[curr*2]) t = curr*2+1; logic, missing the boundary check for whether the right child exists (curr*2+1) <= sz. If the current node only has a left child, the code will forcibly read the residual dirty data at position sz+1 for size comparison, causing the sinking path of the max-heap to be hijacked by dirty data, leading to heap property collapse or random logical errors.

Classic NOIP/Luogu Problems

1. Luogu P1177 [Template] Quick Sort / Sorting

2. Luogu P1090 [NOIP2004 Advanced Group] Merging Fruits / [USACO06NOV] Fence Repair G

Original Source: local://5.1

[h] Back to Home