NeFut Logo NeFut
Admin Login

In-Depth Analysis of Binary Heap Structure and Operations

Published at: 2026-05-27 09:17 Last updated: 2026-07-05 12:01
#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 lies in utilizing the contiguous memory space of an array to implicitly maintain the tree topology through arithmetic relationships of indices, thereby eliminating pointer overhead.

For a complete binary tree rooted at index $1$, the topological geometric relationships of any node $i$ satisfy:

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

$$\forall i > 1, \quad \text{heap}[\lfloor i/2 \rfloor] \ge \text{heap}[i]$$

The dynamic maintenance operations of a 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 upward along the parent chain until the total order relationship is satisfied.
  2. Shift Down: When popping the highest-priority element from the top of the heap, the last element from the bottom is moved to the top, and then this element is swapped downward along the path of the larger of its two children until the subtree property is restored.

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 under examination be $curr$:

2. Pop Operation (Deletion) and Shift Down Derivation

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


C++ Core Template Code (NOI Style)

const int MAXN = 100005;
int heap[MAXN], sz = 0;

void push(int val) {
    heap[++sz] = val; // Place the new element at the bottom of the heap
    int curr = sz;
    // Shift Up: must ensure curr > 1 to avoid out-of-bounds access to heap[0]
    while (curr > 1 && heap[curr] > heap[curr / 2]) {
        swap(heap[curr], heap[curr / 2]);
        curr /= 2; // Move up along the parent path
    }
}

void pop() {
    if (sz == 0) return;
    heap[1] = heap[sz--]; // Overwrite the top with the bottom element and decrement the size
    int curr = 1;
    // Shift Down: continue as long as a left child exists
    while ((curr * 2) <= sz) {
        int t = curr * 2; 
        // Critical boundary check: must first confirm t + 1 <= sz to prevent out-of-bounds reading of residual dirty data
        if (t + 1 <= sz && heap[t + 1] > heap[t]) {
            t++; // Lock in the larger of the left and right children
        }
        if (heap[curr] >= heap[t]) {
            break; // Parent already satisfies heap property, terminate sinking early
        }
        swap(heap[curr], heap[t]);
        curr = t; // Update the current node index and continue iterating
    }
}

int top() {
    return heap[1]; // O(1) return of the heap top extremum
}

NOIP Practical Pitfall Guide

  1. Building the heap with array indices starting from 0 leads to calculation collapse Some contestants habitually place the heap top at heap[0]. In this case, the left child calculation becomes $2i+1$, the right child becomes $2i+2$, and the parent becomes $\lfloor (i-1)/2 \rfloor$. When retrieving the parent node, if $i=0$, the computation $(0-1)/2$ under C++'s rounding toward zero rule still yields 0, which directly triggers an infinite upward loop. Therefore, handwritten heaps must strictly start storage from index 1.

  2. Shift Down left and right child selection logic short circuit When performing Shift Down, directly writing the comparison logic if (heap[curr*2+1] > heap[curr*2]) misses the boundary check for the existence of the right child: $(curr \times 2 + 1) \le sz$. If the current node only has a left child, the code will forcibly read residual dirty data at position sz+1 for comparison. This causes the sinking path to be hijacked by dirty data, leading to heap property corruption.


Classic NOIP/Luogu Problems

1. Luogu P1177 [Template] Sorting

Core Code (Min-Heap Sorting Application):

// Assume the max-heap template above has been modified to a MinHeap
MinHeap my_heap;
for (int i = 1; i <= n; ++i) {
    my_heap.push(a[i]); // Insert all elements into the heap
}
for (int i = 1; i <= n; ++i) {
    a[i] = my_heap.top(); // Retrieve the current global minimum in order
    my_heap.pop();
}

2. Luogu P1090 [NOIP2004 Advanced Group] Merging Fruits

Core Code (STL Priority Queue Implementation):

#include <queue>
#include <vector>

// Define STL min-heap
priority_queue<int, vector<int>, greater<int>> pq;

int ans = 0;
// Assume the initial weights of the n fruit piles have all been pushed into pq
for (int i = 1; i < n; ++i) { // Merging n piles requires n-1 operations
    int a = pq.top(); pq.pop(); // Retrieve the smallest pile
    int b = pq.top(); pq.pop(); // Retrieve the second smallest pile

    ans += (a + b);       // Accumulate the physical effort of this merge
    pq.push(a + b);       // Re-insert the merged new pile
}
// After the loop ends, ans is the minimum total physical effort

[h] Back to Home