ExamplesBy LevelBy TopicLearning Paths
034 Intermediate

034 — Construct a Complete Binary Tree

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "034 — Construct a Complete Binary Tree" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. A complete binary tree of n nodes (OCaml 99 Problems #34) fills levels from left to right — every level except possibly the last is fully filled, and the last level has all nodes to the left. Key difference from OCaml: 1. **Array representation**: Complete binary trees map perfectly to arrays (level

Tutorial

The Problem

A complete binary tree of n nodes (OCaml 99 Problems #34) fills levels from left to right — every level except possibly the last is fully filled, and the last level has all nodes to the left. This is the structural property of binary heaps and array-based trees. Index i has children at 2i+1 and 2i+2; parent at (i-1)/2.

Complete binary trees are used in heap data structures (priority queues: BinaryHeap in Rust's standard library), binary indexed trees (Fenwick trees for prefix sums), and segment trees. The construction algorithm distributes n nodes optimally to minimize height, which is ⌊log₂(n)⌋ + 1.

🎯 Learning Outcomes

  • • Construct a complete binary tree from n node values
  • • Understand the relationship between node count, tree height, and completeness
  • • Use the recursive formula: left subtree gets (n-1)/2 nodes (or (n)/2 depending on fullness of last level)
  • • Verify completeness: all levels full except possibly the last, which is left-justified
  • • Connect complete binary trees to heap data structures
  • • Build a complete binary tree using the formula: left subtree gets ceiling(n/2) nodes, right gets floor(n/2)
  • • Verify count_nodes(complete_binary_tree(n)) == n for all n in tests
  • Code Example

    #![allow(clippy::all)]
    // Placeholder — pending conversion

    Key Differences

  • Array representation: Complete binary trees map perfectly to arrays (level-order indexing). Rust's BinaryHeap uses Vec internally. The recursive tree is rarely used in practice for heaps.
  • Height calculation: For n nodes, height is ⌊log₂(n)⌋. Rust: (n as f64).log2().floor() as usize. OCaml: int_of_float (log (float_of_int n) /. log 2.0).
  • Left vs right subtree size: The exact formula for how many nodes go to each subtree depends on which level is partial. Off-by-one errors here produce trees that are complete but not left-justified.
  • Heapify: Rust's standard BinaryHeap::from(vec) uses the Floyd heapify algorithm (O(n)) rather than constructing a complete tree first. Understanding the tree structure helps reason about heap operations.
  • Complete vs perfect: A complete binary tree has all levels fully filled except possibly the last, which is filled left-to-right. A perfect tree has all leaves at the same depth. The complete_binary_tree n generates the canonical complete binary tree with n nodes.
  • Recursive construction: if n <= 0 then Leaf else Node((), complete_binary_tree(n/2), complete_binary_tree(n - n/2)) splits n nodes between left and right subtrees. The left subtree gets the larger share for left-filling.
  • **() as value type:** The OCaml version uses unit () as the node value — only the structure matters, not the payload. Rust uses () analogously: Tree<()> for structure-only trees.
  • Left-heavy filling: When n is odd, the left subtree gets one more node than the right. This ensures the last level fills left-to-right, which is the defining property of a complete binary tree.
  • OCaml Approach

    OCaml's version: let rec complete_binary_tree n = if n = 0 then Leaf else let l = (n - 1) / 2 + (if (n - 1) mod 2 > 0 then 1 else 0) in Node ('x', complete_binary_tree l, complete_binary_tree (n - 1 - l)). The formula ensures the left subtree is at least as large as the right, maintaining left-justification.

    Full Source

    #![allow(clippy::all)]
    // Placeholder — pending conversion

    Deep Comparison

    OCaml vs Rust: Complete Tree

    Overview

    See the example.rs and example.ml files for detailed implementations.

    Key Differences

    AspectOCamlRust
    Type systemHindley-MilnerOwnership + traits
    MemoryGCZero-cost abstractions
    MutabilityExplicit refmut keyword
    Error handlingOption/ResultResult<T, E>

    See README.md for detailed comparison.

    Exercises

  • Verify completeness: Write is_complete<T>(tree: &Tree<T>) -> bool that checks if a tree is complete. Use level-order traversal — once you see a non-full node, all subsequent nodes must be leaves.
  • From sorted array: Build a complete binary search tree from a sorted Vec<i32>. For a sorted array, the median becomes the root, left half becomes the left subtree, right half the right subtree.
  • Heap operations: Using your complete tree, implement heap_insert and heap_extract_min that maintain the heap property. Compare with Rust's built-in BinaryHeap.
  • Validate completeness: Write is_complete<T>(tree: &Tree<T>) -> bool that checks whether a given tree is a complete binary tree (all levels full except possibly last, last filled left-to-right).
  • N-th node: In a complete binary tree with n nodes, the nodes can be numbered 1..n in BFS order. Write nth_node<T: Clone>(tree: &Tree<T>, n: usize) -> Option<T> returning the n-th node's value.
  • Open Source Repos