3.4.10 · D2Trees

Visual walkthrough — Heap — max-heap and min-heap properties

2,151 words10 min readBack to topic

We use a max-heap throughout (biggest on top). Everything flips symmetrically for a min-heap, and we point out exactly where.


Step 1 — Start with the shape: a tree with no gaps

WHAT. Draw circles (call them nodes), each holding a number. Connect each node to at most two nodes below it (its children). The single node at the very top with no node above it is the root.

WHY this shape and not any tree? We want the tree to be as shallow as possible, because later every operation walks from top to bottom. A tree is shallowest when we never leave a hole: we fill each level completely, top to bottom, and inside a level we fill left to right. A tree filled this way is called a complete binary tree (see Complete Binary Tree).

PICTURE. Look at the figure. The black nodes are placed in reading order — left to right, then drop a level. There is no gap anywhere before the last node.

Figure — Heap — max-heap and min-heap properties

Step 2 — Read the tree into a flat array

WHAT. Number the nodes in that same reading order: root is , its two children are and , the next level is , and so on. Now just store the values in a plain list at those numbered positions.

WHY an array and not pointer-linked nodes? Because there are no gaps (Step 1 guaranteed that), every slot is used. A gapless numbering means we never waste a slot, so a contiguous array holds the whole tree — no node objects, no pointers, and it sits nicely in cache.

PICTURE. The red arrows trace the reading order that maps each tree node onto one array cell. The tree above and the strip below hold the same data.

Figure — Heap — max-heap and min-heap properties

Step 3 — Derive the child and parent formulas by counting slots

WHAT. We want, for a node at index , the indices of its left child, right child, and parent — using only arithmetic.

WHY count slots? Because the numbering is just "how many nodes come before me." If we count how many child-slots are used up before node 's own children, we land exactly on the child's index. This is pure counting — no geometry needed, but the picture makes the count visible.

The count, term by term. Nodes come before node — that is nodes. Each earlier node owns exactly child-slots, so child-slots are filled before node 's children begin. The root occupied slot (index ) that isn't anyone's child, which shifts everything by one. Putting it together:

To get the parent, we undo the child formula. If some node is a left child, , so . If it's a right child, , so . Both collapse to one formula once we round down:

where means "round down to the nearest whole number" — it swallows the difference between the and cases.

PICTURE. The red braces show the block of child-slots sitting before node 's children; the arrow lands on .

Figure — Heap — max-heap and min-heap properties

Step 4 — Add the value rule: parent beats child, everywhere

WHAT. Now impose the heap-order property: for every parent–child pair, Each symbol: is "the value stored at that index," is "greater than or equal to." For a min-heap, flip the single symbol to .

WHY only parent vs child, not everything? Because we only ever need one fast answer: what is the biggest element? If every parent dominates its own children, then by following "bigger than" links upward, the very top must be the biggest of all — so the maximum sits at the root and peeking costs . We deliberately do not compare siblings, because ordering siblings would cost work we never cash in.

PICTURE. Green-highlighted (here red-accent) edges are the checked relations — each parent to each child. Notice the two children of a node are not connected to each other: siblings are unordered.

Figure — Heap — max-heap and min-heap properties

Step 5 — Why the tree is short: the height bound

WHAT. Count how many nodes fit in a complete tree of height (height = number of edges on the longest root-to-leaf path).

WHY do we care about height? Because every fix we do (Step 6) walks one root-to-leaf path. If height is small, every operation is fast. So we must prove height is small.

The count. Level has node, level has , level has — level has . A tree of height has at least the root of the last level (so nodes, when the last level holds just ) and at most a completely full last level:

Taking of the left inequality gives , and the right gives , pinning it to

Here answers "how many times can I double before reaching ?" — exactly the number of levels. See Big-O Notation for why this makes operations .

PICTURE. The red path is the longest root-to-leaf walk; its length grows like as the tree widens.

Figure — Heap — max-heap and min-heap properties

Step 6 — Insert an element: append, then sift-up

WHAT. To add a value, place it in the next free array slot (the next leaf), then repeatedly compare it with its parent and swap up while it is too extreme for its spot.

WHY append first? The only slot that keeps the tree complete (Step 1) is the next empty leaf — filling any other spot would leave a gap. So shape forces the landing spot; then we repair order.

WHY compare with the parent, not children? A freshly appended value may be bigger than its parent (in a max-heap) — it is too big for a low position. Swapping with the parent moves it toward the root, exactly the direction a too-big value should travel.

PICTURE. We insert into the max-heap [9, 8, 7, 5, 4]. The red node is the traveller; each panel shows one swap up its ancestor path.

Figure — Heap — max-heap and min-heap properties

Trace it:

  • Append at index . , value . Is ? No → swap. Array [9,8,10,5,4,7].
  • Now is at index . , value . Is ? No → swap. Array [10,8,9,5,4,7].
  • Index is the root → stop. is now the max on top.

Step 7 — The mirror case (sift-down) and the degenerate cases

WHAT. The opposite of insert: if a value is too small for a high position (e.g. after we overwrite the root), push it down. Compare with the larger child and swap while a child beats it. This is max-heapify, the engine of Build-Heap (Heapify) O(n) and Heapsort.

WHY swap with the larger child? After the swap, the promoted child becomes the new parent — it must dominate the other child too. Only the larger child is guaranteed to be its sibling, so swapping with it keeps the rule intact automatically.

Degenerate cases you must never hit unprepared:

  • Only one child. A node whose right index has just a left child — compare against that one only. (Example 1 in the parent, , skips index .)
  • Leaf node. If , the node has no children — sift-down stops instantly.
  • (empty) or (root only). No parent–child pairs exist, so any such array is trivially a valid heap — nothing to check.
  • Equal values. satisfies both and , so ties never cause a swap; duplicates are perfectly legal.

PICTURE. Root in [3,9,8,5,4] sifts down. The red node is the sinking value; note we always pick the larger child (highlighted).

Figure — Heap — max-heap and min-heap properties

Trace it: val , children → larger is (index ), swap → [9,3,8,5,4]. Now val , children → larger (index ), swap → [9,5,8,3,4]. Index is a leaf → stop. Valid max-heap.


The one-picture summary

Everything on one canvas: the shape (complete tree, no gaps) ↔ the array (via ), the order rule on every parent–child edge, and the two repair motions — sift-up for insert, sift-down for heapify — each walking one path of length .

Figure — Heap — max-heap and min-heap properties
Recall Feynman retelling — the whole walkthrough in plain words

We started by drawing a tree that never leaves a hole — fill each row completely, left to right. Because there are no holes, we can just list the nodes in reading order into an ordinary array, and a little counting tells us that a node at spot has its kids at and , and its parent at . Then we added one rule: every parent must be at least as big as its children — but we never compare two kids to each other, so the array is not fully sorted; only the boss-beats-subordinate chains hold. That one rule forces the biggest number to the very top, so peeking is instant. Since each row doubles, a tree of nodes is only about rows tall. When a newcomer arrives it stands at the next empty spot (to keep the shape), then keeps swapping upward with its boss until someone above is bigger — a short climb because the tree is short. The reverse move, when a too-small value sits too high, is to sink it down, always swapping with the bigger child so the new boss still beats the other kid. Edge cases: a node with one child, a leaf, an empty or single-element array, and ties — all handled by the same rule doing nothing surprising.


Connections

  • 3.4.10 Heap — max-heap and min-heap properties (Hinglish) — the parent topic (Hinglish).
  • Complete Binary Tree — the no-gap shape from Step 1.
  • Priority Queue — cashes in the fast insert/extract.
  • Build-Heap (Heapify) O(n) — repeated sift-down (Step 7).
  • Heapsort — build-heap then extract the root repeatedly.
  • Binary Search Tree — contrast: BST does order siblings.
  • Big-O Notation — why one path = .