Binary tree
A binary tree is a fundamental hierarchical data structure in computer science consisting of a finite set of nodes, where each node contains a data element and at most two children, referred to as the left and right subtrees.[1] The structure is either empty (with no nodes) or comprises a root node connected to two disjoint binary subtrees, enabling recursive organization of data.[2] This design allows for efficient representation of relationships where order matters, such as in searching or sorting algorithms, and forms the basis for more specialized variants.[3]
Binary trees exhibit various structural properties that define their performance and utility. A full binary tree requires every node to have exactly zero or two children, eliminating nodes with a single child to maintain balance in certain operations.[4] In contrast, a complete binary tree fills nodes from left to right level by level, optimizing space usage in array-based implementations like heaps.[5] Binary search trees (BSTs) impose an ordering constraint, where all values in the left subtree are less than the root and those in the right subtree are greater, facilitating logarithmic-time search, insertion, and deletion operations.[1] Degenerate cases, resembling linked lists, occur when each node has only one child, leading to linear performance akin to arrays.[6]
The versatility of binary trees underpins numerous applications across computing domains. They are essential in implementing efficient search mechanisms, such as in databases and file systems for quick hierarchical lookups.[3] Binary search trees enable dynamic data management with average O(log n) complexity for core operations, making them ideal for sorted collections and autocomplete features.[1] Heaps, a type of complete binary tree, support priority queues used in algorithms like Dijkstra's shortest path or scheduling tasks.[7] Additionally, expression trees represent mathematical or syntactic structures, aiding in parsing compilers and evaluating arithmetic operations recursively.[7]
Definitions
Recursive definition
A binary tree can be defined recursively as a data structure that is either empty or consists of a root node together with a left subtree and a right subtree, where each subtree is itself a binary tree.[8][1] This recursive structure captures the self-similar nature of binary trees, enabling them to grow to arbitrary depth by repeatedly applying the definition to the subtrees.[9]
Formally, the set T of binary trees satisfies the recursive equation:
T = \emptyset \cup \{ \text{root} \times T \times T \},
where \emptyset denotes the empty tree, the root is a node, and the Cartesian products represent the left and right subtrees.[10][11] This definition permits nodes to have zero, one, or two children, depending on whether the subtrees are empty or non-empty.
A variant known as a full binary tree imposes stricter recursive rules, where the base case is a single node (a leaf with zero children), and the recursive case requires a root with exactly two non-empty full binary subtrees, ensuring every internal node has precisely two children.[11][12] In contrast, an extended binary tree (or general binary tree allowing unary nodes) relaxes this by permitting subtrees to be empty in the recursive step, thus allowing nodes with one child.[11][13]
For example, consider a binary tree with root node A, a left subtree consisting of node B (a leaf), and an empty right subtree; this unfolds recursively as the left subtree of A satisfying the base case for B, while the right satisfies the empty case, demonstrating a node with one child.[1]
Graph-theoretic definition
In graph theory, a binary tree is a finite rooted directed acyclic graph in which the root node has indegree 0, every non-root node has indegree exactly 1, and every node has outdegree at most 2.[14] The directed edges point from parent nodes to their children, ensuring the structure is connected in the underlying undirected graph and free of directed cycles.[14] This formalization captures the hierarchical organization without allowing loops or multiple paths between nodes.
In an ordered binary tree, the two possible outgoing edges from any node are distinguished as the left child and right child, imposing a linear order on siblings.[15] Unordered binary trees, by contrast, do not distinguish between left and right, treating the children as an unlabeled set.[15] The degree constraints ensure that leaves—nodes with outdegree 0—terminate branches, while internal nodes (non-leaves except possibly the root) have outdegree 1 or 2 and receive exactly one incoming edge, except for the root.[14]
This graph-theoretic perspective provides a rigorous foundation for analyzing binary trees using tools from graph theory, such as connectivity and path properties, distinct from recursive constructions.[14]
For illustration, consider a small ordered binary tree with four nodes: a root r connected to left child a (a leaf) and right child b, where b connects to its left child c (a leaf). The directed edges are r \to a (left), r \to b (right), and b \to c (left), satisfying indegree and outdegree limits with no cycles.
r
/ \
a b
/
c
r
/ \
a b
/
c
Properties
Structural properties
A binary tree consists of nodes connected by edges, where each node except the root has exactly one parent, ensuring a hierarchical structure without multiple incoming connections.[16] The subtrees rooted at the left and right children of any node are disjoint, meaning they share no nodes in common, which maintains the tree's partitioned organization.[17]
The left and right subtrees of a node are structurally independent, allowing each to develop its own configuration without influencing the other, though they may exhibit mirroring symmetries in certain tree variants.[18] In any binary tree with n nodes, the number of edges is exactly n - 1, as each non-root node contributes one incoming edge, forming a connected acyclic graph.[19]
For a full binary tree, where every node has either zero or two children, the total number of nodes n satisfies n = 2l - 1, with l denoting the number of leaves; this relation arises because the number of leaves is one more than the number of internal nodes, as each internal node has two children and the tree has n-1 edges.[20]
Binary trees contain no cycles, a property proven by the uniqueness of paths from the root: suppose a cycle exists; then any node on the cycle would have two distinct paths from the root (one direct and one via the cycle), contradicting the single-parent rule that ensures exactly one path to each node.[21]
Height and size relationships
In binary trees, the height h of a tree is defined as the number of edges on the longest path from the root node to a leaf node. The depth of a node is the number of edges on the path from the root to that node, with the root at depth 0.[5][22]
For a binary tree with n nodes, the minimum height is achieved when the tree is as balanced as possible, such as in a complete binary tree, where h \geq \log_2(n+1) - 1. This bound ensures the tree fills levels from left to right before advancing to deeper levels. In contrast, the maximum height occurs in a degenerate binary tree, resembling a linear chain, where h = n - 1.[23][24]
Given a fixed height h, a binary tree can hold a maximum of $2^{h+1} - 1 nodes, which is the case for a full binary tree where every level is completely filled and every node has either zero or two children. The minimum number of nodes for height h is h + 1, occurring in a skewed configuration with a single path from root to leaf. Nodes in the tree are distributed across levels indexed from 0 (root) to h, where the maximum number of nodes at level k is $2^k.[25][26]
These relationships between height and size directly influence the time complexity of tree operations. For instance, searching for a node requires traversing at most h edges in the worst case, leading to O(h) time, which underscores the importance of minimizing height to improve efficiency in applications like search trees.[27][22]
Types
Complete and perfect binary trees
A full binary tree, also known as a proper binary tree or 2-tree, is defined as a binary tree in which every node has either zero or two children, with no nodes having exactly one child.[22] This structure ensures that all internal nodes branch fully, leading to a strict alternation between internal nodes and leaves.[22]
A complete binary tree is a binary tree in which every level, except possibly the last, is completely filled, and all nodes in the last level are as far to the left as possible.[22] This filling pattern from left to right makes complete binary trees particularly suitable for implementing priority queues like heaps, where the shape facilitates efficient operations.[28] Complete binary trees can have nodes with one child only in the last level, distinguishing them from full binary trees.
A perfect binary tree is a special case where all levels are completely filled, with every internal node having exactly two children and all leaves at the same depth.[29] For a perfect binary tree of height h (where the root is at height 0), the total number of nodes is given by the formula $2^{h+1} - 1.[29] Every perfect binary tree is both full and complete, but the converse does not hold.[30]
To illustrate the differences, consider simple textual representations:
Full binary tree example (all nodes have 0 or 2 children, but levels may not be filled evenly):
A
/ \
B C
/ \
D E
A
/ \
B C
/ \
D E
This tree is full but neither complete nor perfect due to uneven level filling.
Complete binary tree example (levels filled left-to-right, last level partial):
A
/ \
B C
/ \ /
D E F
A
/ \
B C
/ \ /
D E F
Here, the last level has nodes D, E, and F filled from the left, making it complete but not perfect.
Perfect binary tree example (all levels fully filled):
A
/ \
B C
/ \ / \
D E F G
A
/ \
B C
/ \ / \
D E F G
This structure has all seven nodes at heights 0 through 2, fully populating each level.
A key property of complete binary trees is their compatibility with array-based storage, where nodes are indexed sequentially in level order, allowing parent-child access via simple arithmetic (e.g., for a node at index i, its left child is at $2i + 1 and right at $2i + 2).[31] This representation minimizes space overhead from pointers, though detailed implementations are discussed in array storage contexts.[32]
Balanced binary trees
A balanced binary tree is defined as a binary tree in which the heights of the left and right subtrees of every node differ by at most one.[33] This property ensures that the overall height of the tree remains logarithmic in the number of nodes, preventing degeneration into a linear structure.[34] Self-balancing binary trees automatically maintain this balance through rebalancing operations triggered by insertions and deletions, guaranteeing efficient performance.[35]
AVL trees, named after their inventors Georgy Adelson-Velsky and Evgenii Landis, were the first self-balancing binary search trees, introduced in their 1962 paper on information organization algorithms.[36] They enforce a strict balance factor of at most one for every node by performing single or double rotations after structural modifications to restore height equilibrium.[37] This rigorous maintenance results in a tree height bounded by approximately 1.44 log₂(n + 2) - 0.328 for n nodes,[38] providing worst-case O(log n) time for key operations.[39]
Red-black trees offer a more relaxed balancing approach, originally described by Rudolf Bayer in 1972 as symmetric binary B-trees, later formalized with color attributes. Each node is colored red or black, with properties ensuring no two red nodes are adjacent and that all paths from a node to its descendant leaves contain the same number of black nodes.[40] These rules limit the height to at most twice the height of a perfectly balanced tree, yielding O(log n) operations while allowing fewer rotations than AVL trees during rebalancing.[41]
Splay trees, invented by Daniel Sleator and Robert Tarjan in 1985, achieve balance through amortized analysis rather than strict height constraints.[42] Upon accessing a node, it is "splayed" to the root via a series of rotations, promoting frequently accessed elements and ensuring that any sequence of m operations takes O(m log n + n log n) time in the worst case.[43] This self-adjusting mechanism adapts to access patterns without explicit balance factors, often outperforming other balanced trees for non-uniform distributions.[44]
The primary benefit of balanced binary trees like AVL, red-black, and splay trees is their assurance of O(log n) worst-case or amortized time complexity for insertions, deletions, and searches, in contrast to the potential O(n) degradation in unbalanced binary search trees.[45] This efficiency is crucial for applications requiring dynamic data management, such as databases and file systems, where maintaining logarithmic access times scales with large datasets.[35]
Combinatorics
Enumeration of binary trees
The enumeration of binary trees typically focuses on unlabeled plane binary trees, which are rooted structures where the order of subtrees matters, distinguishing between left and right children, and nodes are indistinguishable except by their positions in the hierarchy.[46]
The number of such distinct binary trees with exactly n nodes is given by the nth Catalan number C_n = \frac{1}{n+1} \binom{2n}{n}.[46] This count arises from considering the root node and partitioning the remaining n-1 nodes between the left and right subtrees. Based on the recursive definition of binary trees, the enumeration satisfies the recurrence relation T(n) = \sum_{i=0}^{n-1} T(i) \, T(n-1-i) for n \geq 1, with base cases T(0) = 1 (empty tree) and T(1) = 1 (single root node).[46] For example, with n=3 nodes, there are C_3 = 5 possible shapes: one where the root has two leaf children; two where the root has only a left child that itself has one leaf child (either left or right); and two symmetric cases where the root has only a right child that itself has one leaf child (either left or right).[46]
These enumerative results have applications in combinatorics and computer science, particularly in counting the possible parse trees for expressions in context-free grammars, where binary trees model operator precedence and associativity.[46] They also inform the design and analysis of sorting networks, where the structural variety of binary trees helps enumerate decision paths in parallel comparison-based sorting architectures.[47]
Catalan numbers in binary trees
The nth Catalan number is defined as
C_n = \frac{1}{n+1} \binom{2n}{n},
where \binom{2n}{n} denotes the central binomial coefficient.[48] This sequence arises in numerous combinatorial contexts, with C_0 = 1, C_1 = 1, C_2 = 2, C_3 = 5, and so on.[49]
In the context of binary trees, the nth Catalan number C_n enumerates the number of distinct rooted plane binary trees with n+1 leaves, where each internal node has exactly two children.[49] Equivalently, C_n counts the number of such binary trees with n internal nodes. This interpretation highlights the recursive structure of binary trees: a tree with n internal nodes consists of a root connected to a left subtree with k internal nodes and a right subtree with n-1-k internal nodes, for k = 0 to n-1, leading to the recurrence C_n = \sum_{k=0}^{n-1} C_k C_{n-1-k} with C_0 = 1.[48]
The ordinary generating function for the Catalan numbers is
C(x) = \sum_{n=0}^{\infty} C_n x^n = \frac{1 - \sqrt{1 - 4x}}{2x},
which satisfies the functional equation C(x) = 1 + x C(x)^2 derived from the recursive decomposition of binary trees.[48] This equation mirrors the way a binary tree is formed by attaching two subtrees to the root, providing a generating function approach to count the trees.
A standard proof of the Catalan number formula in this setting relies on a bijection between rooted plane binary trees with n+1 leaves and valid sequences of n pairs of balanced parentheses.[50] The mapping proceeds recursively: the empty tree corresponds to the empty sequence; otherwise, the sequence for a tree is the left subtree's sequence enclosed in parentheses, followed by the right subtree's sequence. This bijection preserves the recursive structure and directly yields the count C_n, as the number of valid parenthesis sequences is also given by the Catalan formula. An analogous bijection exists with Dyck paths of semilength n, which can be interpreted as the "heights" traversed in a tree's structure, further confirming the enumeration.[48]
An important extension is that C_n also counts the number of full binary trees (where every node has either zero or two children) with exactly $2n+1 nodes, since such trees have n internal nodes and n+1 leaves. This provides a foundational tool for enumerating binary trees, as discussed in the broader enumeration section.
The Catalan numbers are named after the Belgian mathematician Eugène Charles Catalan, who first studied them in 1838 while investigating combinatorial problems related to successive roots and combinations.[51] Although the explicit connection to binary trees emerged later, combinatorial interpretations including tree structures were recognized in 19th-century works building on these foundations.[52]
Representations
Node-based storage
In node-based storage, binary trees are implemented as a collection of dynamically allocated nodes linked via pointers, allowing for flexible representation of arbitrary tree structures. Each node typically stores the associated data value along with two pointers: one to the left child and one to the right child, enabling the hierarchical organization of the tree. This structure supports null pointers for leaves or missing children, ensuring that every node has at most two children. Optionally, an additional parent pointer can be included in each node to simplify navigation and operations such as deletion in bidirectional traversal scenarios.[22][53]
Memory management in this representation relies on dynamic allocation for each node, which incurs overhead beyond the data itself—typically the space for two (or three) pointers, each requiring 4 or 8 bytes depending on the system architecture. This pointer overhead can represent a significant portion of memory usage, especially for trees with small data elements, but it avoids wasting space on non-existent nodes unlike contiguous representations. Dynamic allocation, often handled by language runtimes or manual calls in languages like C, allows the tree to grow or shrink as needed without predefined size limits. However, frequent allocations and deallocations can lead to external memory fragmentation, where free memory becomes scattered in small blocks unsuitable for larger requests.[54]
The primary advantages of node-based storage lie in its adaptability to unbalanced or irregular tree shapes, making it ideal for scenarios where the tree structure evolves unpredictably. Insertion and deletion operations are efficient and intuitive, involving only pointer adjustments rather than shifting elements, which supports O(1) local changes after locating the position. This flexibility is particularly valuable in general-purpose applications, such as implementing binary search trees for dynamic sets or expression trees in compilers.[55][56]
Despite these benefits, node-based storage has notable drawbacks, including reduced cache efficiency due to pointer chasing, where traversing the tree requires jumping between non-contiguous memory locations, leading to frequent cache misses and slower access times compared to contiguous alternatives. The additional pointer storage also increases overall memory footprint, and in unbalanced trees, performance can degrade to linear time in the worst case, mimicking a linked list. To illustrate the node definition, the following pseudocode shows a basic class in a language-agnostic style, commonly used in implementations with languages supporting object-oriented or structured programming:
class [Node](/page/Node) {
[data](/page/Data) // The [value](/page/Value) stored in the [node](/page/Node)
left: [Node](/page/Node) | [null](/page/Null) // Pointer to left [child](/page/Child)
right: [Node](/page/Node) | [null](/page/Null) // Pointer to right [child](/page/Child)
// Optional: [parent](/page/Parent): [Node](/page/Node) | [null](/page/Null) // Pointer to [parent](/page/Parent) [node](/page/Node)
constructor([value](/page/Value)) {
this.[data](/page/Data) = [value](/page/Value)
this.left = [null](/page/Null)
this.right = [null](/page/Null)
// this.[parent](/page/Parent) = [null](/page/Null)
}
}
class [Node](/page/Node) {
[data](/page/Data) // The [value](/page/Value) stored in the [node](/page/Node)
left: [Node](/page/Node) | [null](/page/Null) // Pointer to left [child](/page/Child)
right: [Node](/page/Node) | [null](/page/Null) // Pointer to right [child](/page/Child)
// Optional: [parent](/page/Parent): [Node](/page/Node) | [null](/page/Null) // Pointer to [parent](/page/Parent) [node](/page/Node)
constructor([value](/page/Value)) {
this.[data](/page/Data) = [value](/page/Value)
this.left = [null](/page/Null)
this.right = [null](/page/Null)
// this.[parent](/page/Parent) = [null](/page/Null)
}
}
This representation is a staple for linked data structures in binary trees, contrasting with array-based storage that suits denser, complete trees but lacks the same structural versatility.[56]
Array-based storage
Array-based storage represents a binary tree using a single contiguous array, eliminating the need for explicit pointers between nodes and making it particularly suitable for complete binary trees where nodes are filled level by level from left to right.[57] A common convention places the root node at index 1 (with index 0 often left unused as a sentinel), the left child of a node at index i at $2i, and the right child at $2i + 1; alternatively, 0-based indexing places the root at index 0, left child at $2i + 1, and right child at $2i + 2. This indexing allows direct computation of parent-child relationships using simple arithmetic: for 1-based, the parent of a node at index i (for i > 1) is at \lfloor i/2 \rfloor; for 0-based, it is at \lfloor (i - 1)/2 \rfloor.[58]
The space complexity is O(n) for a tree with n nodes, as each node occupies one array slot without additional overhead for pointers, assuming the array is sized appropriately for the complete structure.[57] This contiguous layout enhances cache efficiency, enabling faster access patterns in memory compared to pointer-based methods, and supports constant-time computation for locating children or parents.[59] However, this representation assumes the tree is complete or nearly complete; sparse trees waste space in unused array positions, and fixed-size arrays limit dynamic growth without resizing, which can introduce overhead.[57]
For insertion in a complete binary tree, a new node is added at the next available position in level order (the end of the current array), maintaining the complete property, after which adjustments like heapifying up may be applied if the tree enforces additional ordering (e.g., in binary heaps).
Consider a small complete binary tree with root value 1, left child 2, right child 3, and 2 having a left child 4. This is represented in the array as [-, 1, 2, 3, 4], where the leading - denotes the unused index 0, and positions 1 through 4 hold the node values in level order.
Encodings
Succinct encodings
Succinct encodings for binary trees aim to represent the structure of an n-node ordered binary tree using 2n + o(n bits of space, approaching the information-theoretic lower bound of approximately 2n bits needed to distinguish among the Catalan number C_n of possible structures, in contrast to the Θ(n log n) bits required by conventional node-based storage that allocates words for left and right child pointers per node.[60]
One foundational approach is the balanced parenthesis (BP) representation, which captures the tree's topology via a depth-first traversal. During traversal, an opening parenthesis '(' is recorded upon descending to a child subtree, and a closing parenthesis ')' upon ascending from a node; leaves contribute a single pair '()'. This yields exactly 2n bits for n nodes, as each node corresponds to one opening and one closing parenthesis. For instance, a root with a left leaf child and a right child that itself has left and right leaf children encodes as "(()(()()))", where the inner "()" (after the initial open) denotes the left leaf, and "(()())" the right subtree.[60]
To enable efficient querying on this bit sequence, succinct encodings incorporate rank and select data structures. The rank operation computes the number of opening parentheses up to a given position, while select identifies the position of the k-th opening parenthesis. Preprocessing the BP sequence with o(n) extra bits—typically O(n log log n / log n)—allows both operations in O(1) time, supporting key tree navigations such as finding a node's parent, left or right child, depth, or subtree size in constant time.[60]
These encodings find application in succinct data structure libraries, such as the Succinct Data Structure Library (SDSL), where they enable compact storage and fast access for large-scale tree-based indexes in bioinformatics and information retrieval.
Encoding general trees as binary trees
One common technique for encoding general ordered trees, including m-ary trees where m can vary, as binary trees is the first-child/next-sibling representation, also known as the left-child/right-sibling or Knuth transform.[29] In this method, each node in the binary tree structure uses its left pointer to reference the first child of the corresponding node in the original general tree, and its right pointer to reference the next sibling in the ordered list of children.[4] This approach requires only two pointers per node, providing O(1) additional space overhead beyond the original tree's node storage, regardless of the degree m, by chaining siblings into a linked list via right pointers.[61]
The method was proposed by Donald Knuth in the 1960s to enable uniform algorithmic treatment of multi-way trees using binary tree operations, as detailed in his seminal work on fundamental algorithms. It preserves the ordered structure of children in the general tree, maintaining ancestry relationships through the left-child chains, which form the subtrees, while the right-sibling links ensure traversal follows the original sibling order. This encoding is reversible, allowing reconstruction of the original general tree from the binary representation without loss of information.
A key advantage is the ability to apply standard binary tree traversal and manipulation algorithms—such as in-order or level-order traversals—to general trees, facilitating implementation in systems optimized for binary structures and reducing code complexity for handling variable-arity nodes.[62] For instance, depth-first traversal in the encoded binary tree can mimic pre-order traversal of the original tree by following left subtrees (children) before right links (siblings).
To illustrate, consider a simple ternary tree with root A having children B, C, and D, where B has child E, C has children F and G, and D is a leaf. In the binary encoding:
- A's left points to B (first child), and B's right points to C (B's next sibling), C's right points to D (C's next sibling), D's right is null.
- B's left points to E (B's first child), E's right is null.
- C's left points to F (C's first child), F's right points to G (F's next sibling), G's right is null.
- D's left is null (no children).
This remapping transforms the multi-child structure into a binary tree where left edges represent parent-child ancestry and right edges represent sibling adjacency, fully preserving the original hierarchy and order.[29]
Operations
Insertion and deletion
Insertion in a binary tree, particularly in the context of binary search trees (BSTs), involves adding a new node while preserving the tree's structure and properties. For a BST, the algorithm starts at the root and compares the new key with the current node's key, traversing left if smaller or right if larger, until reaching a null child pointer where the new node is attached as a leaf. This recursive placement ensures the BST property: all nodes in the left subtree have keys less than the parent, and all in the right subtree have keys greater. The operation takes O(h) time, where h is the tree's height, which is O(log n) on average for balanced trees but O(n) in the worst case for skewed ones.[63]
For an empty tree, insertion creates the new node as the root. Pseudocode for BST insertion, adapted from standard recursive implementations, is as follows:
[function](/page/Function) insert([root](/page/Root), key):
if [root](/page/Root) is [null](/page/Null):
return new [Node](/page/Node)(key)
if key < [root](/page/Root).key:
[root](/page/Root).left = insert([root](/page/Root).left, key)
elif key > [root](/page/Root).key:
[root](/page/Root).right = insert([root](/page/Root).right, key)
return [root](/page/Root)
[function](/page/Function) insert([root](/page/Root), key):
if [root](/page/Root) is [null](/page/Null):
return new [Node](/page/Node)(key)
if key < [root](/page/Root).key:
[root](/page/Root).left = insert([root](/page/Root).left, key)
elif key > [root](/page/Root).key:
[root](/page/Root).right = insert([root](/page/Root).right, key)
return [root](/page/Root)
This approach handles duplicates by either ignoring them or placing them in a specified subtree, depending on the implementation.[64]
Deletion in a BST removes a node with a given key while maintaining the search property, involving three cases based on the number of children. If the node is a leaf (no children), it is simply removed by setting its parent's pointer to null. For a node with one child, the child replaces the node directly, bypassing it in the tree structure. In the case of two children, the node's value is replaced with its inorder successor (the minimum value in the right subtree), and then the successor node—which has at most one child—is deleted. This successor replacement preserves the BST order. The time complexity remains O(h), similar to insertion.[63]
Pseudocode for BST deletion illustrates these cases:
function delete(root, key):
if root is null:
return root
if key < root.key:
root.left = delete(root.left, key)
elif key > root.key:
root.right = delete(root.right, key)
else:
# Node found
if root.left is null:
return root.right
elif root.right is null:
return root.left
else:
successor = findMin(root.right)
root.key = successor.key
root.right = delete(root.right, successor.key)
return root
function findMin(node):
while node.left is not [null](/page/Null):
node = node.left
return node
function delete(root, key):
if root is null:
return root
if key < root.key:
root.left = delete(root.left, key)
elif key > root.key:
root.right = delete(root.right, key)
else:
# Node found
if root.left is null:
return root.right
elif root.right is null:
return root.left
else:
successor = findMin(root.right)
root.key = successor.key
root.right = delete(root.right, successor.key)
return root
function findMin(node):
while node.left is not [null](/page/Null):
node = node.left
return node
Root deletion requires updating the root reference to the appropriate replacement. Edge cases include attempting to delete from an empty tree (no operation) or handling equal keys consistently with insertion policy.[64]
In self-balancing binary trees like AVL trees, insertion and deletion may disrupt balance, addressed by rotations to keep subtree heights differing by at most one. After modification, balance factors are checked along the path; if exceeding 1 in absolute value, single (left or right) or double rotations are applied at the lowest unbalanced ancestor. These rotations, such as right rotation for left-heavy subtrees, restore balance in O(1) additional time per operation, ensuring amortized O(log n) complexity. The AVL tree was introduced in a seminal 1962 paper by Adelson-Velsky and Landis.[36]
Traversal methods
Binary tree traversal refers to the process of visiting each node in the tree exactly once in a systematic manner, enabling operations such as printing, searching, or processing node data. These methods are fundamental for analyzing and manipulating binary trees, with algorithms typically achieving O(n time complexity, where n is the number of nodes, since each node is processed once. Space complexity varies by approach, often O(h) for recursive methods, where h is the tree height, due to the call stack or explicit data structures like stacks or queues.[65][66]
Depth-First Traversals
Depth-first traversals (DFS) explore as far as possible along each branch before backtracking, encompassing three primary variants: preorder, inorder, and postorder. These can be implemented recursively or iteratively using a stack, with recursive versions being simpler but consuming O(h) space in the worst case for skewed trees. Iterative versions mimic recursion explicitly with a stack, also requiring O(h) space.[67][68]
Preorder traversal visits the root node first, followed by the left subtree, then the right subtree. This order is useful for creating a copy of the tree or serializing it to prefix notation. The recursive pseudocode is as follows:
function preorder([root](/page/Root)):
if [root](/page/Root) is [null](/page/Null):
return
visit [root](/page/Root)
preorder([root](/page/Root).left)
preorder([root](/page/Root).right)
function preorder([root](/page/Root)):
if [root](/page/Root) is [null](/page/Null):
return
visit [root](/page/Root)
preorder([root](/page/Root).left)
preorder([root](/page/Root).right)
An iterative version uses a stack to push the root, then repeatedly pops a node, visits it, and pushes its right and left children in reverse order to maintain the traversal sequence.[65][69]
Inorder traversal processes the left subtree, then the root, followed by the right subtree. In a binary search tree, this yields nodes in sorted (non-decreasing) order. The recursive pseudocode is:
function inorder(root):
if root is null:
return
inorder(root.left)
visit root
inorder(root.right)
function inorder(root):
if root is null:
return
inorder(root.left)
visit root
inorder(root.right)
Iterative inorder uses a stack to simulate the recursion, pushing nodes while traversing left and popping to visit when no left child exists, then moving right.[70][66]
Postorder traversal visits the left subtree, then the right, and finally the root. This is particularly useful for operations requiring processing of children before parents, such as computing subtree sizes or deleting trees. The recursive pseudocode is:
function postorder(root):
if root is null:
return
postorder(root.left)
postorder(root.right)
visit root
function postorder(root):
if root is null:
return
postorder(root.left)
postorder(root.right)
visit root
Iterative postorder is more complex, often using two stacks—one for traversal and one for output—or a single stack with additional tracking of visited children.[65][69]
Breadth-First Traversal
Breadth-first traversal, also known as level-order traversal, visits nodes level by level, starting from the root and proceeding left to right across each level. This uses a queue to enqueue the root, then repeatedly dequeues a node, visits it, and enqueues its children. The pseudocode is:
function levelOrder([root](/page/Root)):
if [root](/page/Root) is [null](/page/Null):
return
[queue](/page/Queue) = new [Queue](/page/Queue)()
[queue](/page/Queue).enqueue([root](/page/Root))
while [queue](/page/Queue) is not empty:
current = [queue](/page/Queue).dequeue()
visit current
if current.left is not [null](/page/Null):
[queue](/page/Queue).enqueue(current.left)
if current.right is not [null](/page/Null):
[queue](/page/Queue).enqueue(current.right)
function levelOrder([root](/page/Root)):
if [root](/page/Root) is [null](/page/Null):
return
[queue](/page/Queue) = new [Queue](/page/Queue)()
[queue](/page/Queue).enqueue([root](/page/Root))
while [queue](/page/Queue) is not empty:
current = [queue](/page/Queue).dequeue()
visit current
if current.left is not [null](/page/Null):
[queue](/page/Queue).enqueue(current.left)
if current.right is not [null](/page/Null):
[queue](/page/Queue).enqueue(current.right)
This method requires O(w) space, where w is the maximum width of the tree (number of nodes at the widest level), in addition to O(n) time. It is distinct from DFS in that it prioritizes shallower nodes over deeper ones.[65][71]
Morris Traversal
Morris traversal provides an in-order traversal using O(1) extra space by temporarily modifying the tree's structure through "threading"—linking nodes to their in-order successors via unused right-child pointers—then restoring the links during traversal. Introduced by J. H. Morris, it achieves O(n) time by visiting each node a constant number of times on average, though worst-case analysis shows up to O(n) edge traversals due to pointer adjustments. The algorithm initializes with the leftmost node, finds successors by following right pointers until null, threads them, visits, and unthreads upon backtracking. Pseudocode for Morris in-order is:
function morrisInorder([root](/page/Root)):
current = [root](/page/Root)
while current is not [null](/page/Null):
if current.left is [null](/page/Null):
visit current
current = current.right
else:
predecessor = current.left
while predecessor.right is not [null](/page/Null) and predecessor.right != current:
predecessor = predecessor.right
if predecessor.right is [null](/page/Null):
predecessor.right = current // thread
current = current.left
else:
predecessor.right = [null](/page/Null) // unthread
visit current
current = current.right
function morrisInorder([root](/page/Root)):
current = [root](/page/Root)
while current is not [null](/page/Null):
if current.left is [null](/page/Null):
visit current
current = current.right
else:
predecessor = current.left
while predecessor.right is not [null](/page/Null) and predecessor.right != current:
predecessor = predecessor.right
if predecessor.right is [null](/page/Null):
predecessor.right = current // thread
current = current.left
else:
predecessor.right = [null](/page/Null) // unthread
visit current
current = current.right
This method is non-recursive and avoids stacks or queues but alters the tree temporarily, which may not suit immutable structures.[72][73]
Applications
In data structures
Binary search trees (BSTs) are a fundamental extension of binary trees used for organizing and storing ordered sets of keys, where each node contains a key and the keys in the left subtree are less than the node's key, while those in the right subtree are greater. This structure enables efficient searching, insertion, and deletion operations, with an average time complexity of O(\log n) for balanced trees, making it suitable for dynamic data management in applications requiring ordered access.
Heaps, specifically binary heaps, represent another key application of binary trees as complete binary trees that satisfy the heap property: in a min-heap, each parent node is less than or equal to its children, and in a max-heap, it is greater than or equal to them. This structure underpins priority queues, allowing efficient extraction of the minimum or maximum element in O(\log n) time and insertion in O(\log n) time, which is essential for scheduling and resource allocation tasks. The binary heap was originally introduced as part of the heapsort algorithm.
Tries, or prefix trees, can be implemented as binary trees when handling binary data or bit-level representations of strings, where each node represents a bit position and branches correspond to 0 or 1 values. This specialization facilitates fast prefix-based searches and is particularly useful for storing and retrieving strings or keys with common prefixes, such as in dictionary implementations or autocomplete systems, with search times proportional to the key length rather than the total number of keys. The trie concept was first described in the context of file searching with variable-length keys.[74]
Expression trees model arithmetic or logical expressions as binary trees, with operators as internal nodes and operands as leaves, enabling systematic evaluation, parsing, and optimization in compiler design. For instance, the expression (a + b) \times c is represented with \times as the root, the subexpression (a + b) in the left subtree, and c in the right. This structure supports postfix or prefix traversal for code generation and is integral to syntax analysis in programming languages.
BSTs are commonly employed in dictionary implementations for maintaining sorted word lists with fast lookups, while heaps serve as priority queues in algorithms like Dijkstra's shortest path, where they efficiently manage node priorities during graph traversal. Balanced variants, such as AVL trees or red-black trees, enhance BSTs and heaps by ensuring O(\log n) worst-case performance through rotations or color adjustments.
In algorithms and computing
Binary trees play a pivotal role in various algorithms, enabling efficient decision-making, optimization, and parallel processing in computational problems. In sorting algorithms, tree sort leverages a binary search tree (BST) by inserting elements into the tree structure, which organizes them based on comparisons, followed by an in-order traversal to retrieve the elements in sorted order. This approach achieves an average time complexity of O(n log n) for n elements when the tree remains balanced, making it suitable for scenarios where dynamic insertion during sorting is beneficial. The BST foundation for such sorting traces back to early implementations in the 1960s, as described in foundational work on ordered binary trees.
Huffman coding utilizes binary trees to construct optimal prefix-free codes for data compression, minimizing the average length of encoded symbols based on their frequencies. The algorithm builds a binary tree where leaf nodes represent symbols, and internal nodes merge the two lowest-frequency subtrees iteratively, assigning shorter codes to more frequent symbols along the paths from the root. This results in an encoding that approaches the theoretical entropy limit for lossless compression, widely adopted in standards like ZIP and JPEG. The method was originally developed by David A. Huffman in 1952, revolutionizing information theory applications.
In game theory and artificial intelligence, binary trees can model decision processes with binary branching factors, such as simple two-choice scenarios. For two-player zero-sum games like chess, which feature higher branching factors, general trees represent game states with branches for possible moves, and the minimax algorithm recursively evaluates these trees to select the move that minimizes the maximum possible loss assuming optimal opponent play. Leaf nodes are scored based on outcomes, and values propagate upward: maximizer nodes (one player) choose the highest value, while minimizer nodes (opponent) select the lowest. This search, as applied in chess AI, enables strategic depth but requires pruning techniques like alpha-beta to manage exponential growth. The minimax theorem underpinning this was formalized by John von Neumann in 1928.
Binary trees facilitate divide-and-conquer strategies in parallel computing, exemplified by parallel merge sort, where the recursion naturally maps to a binary tree of tasks. The array is divided into halves recursively, forming a balanced binary tree of subproblems sorted in parallel across processors, then merged bottom-up. This achieves O(log n) depth for parallelism, with total work O(n log n), ideal for distributed systems. Early parallel formulations built on von Neumann's 1945 merge sort, with efficient implementations like Cole's 1988 algorithm optimizing load balance on PRAM models.[75]
In machine learning, binary decision trees serve as classifiers by recursively partitioning data based on feature thresholds, forming a binary tree where each internal node tests a condition, left branches for true, and right for false, until leaves assign class labels. This structure enables interpretable models for binary classification tasks, such as spam detection, by selecting splits that maximize information gain. The ID3 algorithm, introduced by J. Ross Quinlan in 1986, pioneered this approach using entropy to build such trees from training data.[76]
The balanced height of binary trees is crucial for achieving O(n log n) time complexity in algorithms reliant on logarithmic search or traversal depths, such as in dynamic sets or sorting networks. Self-balancing variants maintain height at O(log n) through rotations or restructuring after insertions/deletions, ensuring worst-case efficiency and preventing degeneration to linear chains. This property underpins the scalability of algorithms like those in search-heavy applications. AVL trees, the first such balanced binary search trees, were invented by G. M. Adelson-Velsky and E. M. Landis in 1962, enforcing balance factors to guarantee logarithmic height.
Historically, binary trees emerged in early compilers during the 1950s and 1960s for parsing and syntax analysis, representing expression trees and hierarchical program structures. They enabled efficient code generation by modeling recursive descent and operator precedence in languages like ALGOL 60, forming the basis for syntax-directed translation. This use predated formal data structure nomenclature but influenced compiler design, as seen in Irons' 1961 work on syntax-directed compilers.