Skip to content

Binary Tree

A binary tree is a hierarchical data structure where each node has at most two children, referred to as the left child and the right child. It’s a fundamental structure in computer science, forming the basis for many other advanced data structures like binary search trees, heaps, and tries.

Why is it important? Binary trees excel at representing hierarchical relationships and enabling efficient searching, insertion, and deletion of data. They are crucial for tasks requiring efficient organization and retrieval of information.

Core Concepts:

  • Node: A fundamental unit containing data and pointers (references) to its left and right children.
  • Root: The topmost node of the tree.
  • Leaf Node: A node with no children.
  • Internal Node: A node with at least one child.
  • Parent Node: The node directly above a given node.
  • Subtree: A section of the tree rooted at a particular node.
  • Height: The maximum distance from the root to a leaf node.
  • Depth: The distance from the root to a particular node.
  • Binary Search Tree (BST): A binary tree where the value of every node in the left subtree is less than the value of its parent node, and the value of every node in the right subtree is greater than the value of its parent node. This property allows for efficient searching (O(log n) on average).
  • Full Binary Tree: Every node has either zero or two children.
  • Complete Binary Tree: All levels are completely filled except possibly the last level, and the last level has all keys as left as possible.
  • Perfect Binary Tree: A complete binary tree where all leaf nodes are at the same level.

2. When to Use binary-tree (and When Not To)

Section titled “2. When to Use binary-tree (and When Not To)”

Use Binary Trees when:

  • You need to represent hierarchical data (e.g., file systems, organizational charts).
  • You need efficient searching, insertion, and deletion (especially with BSTs).
  • You need to implement priority queues (using heaps, a specialized binary tree).
  • You need to perform efficient tree traversals (inorder, preorder, postorder).

Don’t use Binary Trees when:

  • You need to frequently access elements by index (arrays are more efficient).
  • You need a simple, linear data structure (linked lists might be simpler).
  • The data doesn’t have a natural hierarchical structure.

3. Core Algorithm / Data Structure Template

Section titled “3. Core Algorithm / Data Structure Template”

A basic binary tree node can be represented as follows:

class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None

Many algorithms operate on binary trees through recursive traversals (inorder, preorder, postorder). A general template for a recursive traversal is:

function traverse(node):
if node is not null:
// Preorder traversal: process node before children
process(node.data)
traverse(node.left) // Recursively traverse left subtree
// Inorder traversal: process node between children
process(node.data)
traverse(node.right) // Recursively traverse right subtree
// Postorder traversal: process node after children
process(node.data)

4. Code Implementations (Python, Java, C++)

Section titled “4. Code Implementations (Python, Java, C++)”
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
def inorder_traversal(node):
if node:
inorder_traversal(node.left)
print(node.data, end=" ")
inorder_traversal(node.right)
root = Node(1)
root.left = Node(2)
root.right = Node(3)
root.left.left = Node(4)
root.left.right = Node(5)
print("Inorder traversal:")
inorder_traversal(root) # Output: 4 2 5 1 3
class Node {
int data;
Node left, right;
Node(int item) {
data = item;
left = right = null;
}
}
class BinaryTree {
void inorder(Node node) {
if (node != null) {
inorder(node.left);
System.out.print(node.data + " ");
inorder(node.right);
}
}
public static void main(String[] args) {
BinaryTree tree = new BinaryTree();
Node root = new Node(1);
root.left = new Node(2);
root.right = new Node(3);
root.left.left = new Node(4);
root.left.right = new Node(5);
System.out.println("Inorder traversal:");
tree.inorder(root); // Output: 4 2 5 1 3
}
}
#include <iostream>
struct Node {
int data;
Node *left, *right;
Node(int item) {
data = item;
left = right = nullptr;
}
};
void inorder(Node* node) {
if (node != nullptr) {
inorder(node->left);
std::cout << node->data << " ";
inorder(node->right);
}
}
int main() {
Node* root = new Node(1);
root->left = new Node(2);
root->right = new Node(3);
root->left->left = new Node(4);
root->left->right = new Node(5);
std::cout << "Inorder traversal: ";
inorder(root); // Output: 4 2 5 1 3
std::cout << std::endl;
return 0;
}
OperationTime Complexity (Average)Time Complexity (Worst)Space Complexity (Worst)
SearchO(log n) (BST)O(n)O(n)
InsertionO(log n) (BST)O(n)O(n)
DeletionO(log n) (BST)O(n)O(n)
Traversal (Inorder, Preorder, Postorder)O(n)O(n)O(n) (recursive)

Best Case: Usually applies to balanced trees (BSTs), where the height is approximately log₂(n).

Average Case: Applies to mostly balanced trees.

Worst Case: Occurs with unbalanced trees (e.g., a skewed tree resembling a linked list), where the height is n.

  • Balancing: For efficient performance, keep your binary search trees balanced (e.g., using self-balancing trees like AVL trees or red-black trees). Unbalanced trees lead to O(n) complexity for many operations.
  • Null Checks: Always check for null pointers before dereferencing them to prevent segmentation faults or null pointer exceptions.
  • Recursion Depth: Deep recursion can lead to stack overflow errors. For very large trees, consider iterative approaches using a stack or queue.
  • Memory Management: In languages like C++, carefully manage memory allocation and deallocation to prevent memory leaks.
  • Understanding Tree Traversals: Master inorder, preorder, and postorder traversals, as they are fundamental to many binary tree algorithms.

Description: Given an integer n, return the number of structurally unique BST’s (binary search trees) which has exactly n nodes of unique values from 1 to n.

High-Level Approach: This problem can be solved using dynamic programming. Let dp[i] represent the number of unique BSTs with i nodes. For a tree with i nodes, we can choose any node from 1 to i as the root. If we choose node k as the root, then the left subtree will have k-1 nodes, and the right subtree will have i-k nodes. The number of unique BSTs with i nodes is the sum of the product of unique BSTs for the left and right subtrees for all possible root choices (k=1 to i). The base cases are dp[0] = 1 and dp[1] = 1. We can iteratively calculate dp[i] using this recursive relationship. This avoids redundant calculations and significantly improves efficiency compared to a naive recursive solution.