Tree Fundamentals
Why Trees Exist
Trees organize data hierarchically, making search, insertion, and deletion efficient while maintaining order. They model natural hierarchies (file systems, organizational charts, decision trees) and enable logarithmic time operations through balanced structures. Trees are the backbone of databases, compilers, and many system designs.
Real impact: Understanding trees enables you to work with hierarchical data efficiently, build decision systems, and optimize search operations from linear to logarithmic time.
What Are Trees?
A tree is a hierarchical data structure with a root node and child nodes, forming a parent-child relationship. Unlike graphs, trees have no cycles and exactly one path between any two nodes.
Key insight: Trees are about hierarchy and efficient access patterns, not just storage.
Tree Terminology
- Root: The top node with no parent
- Parent: Node with children
- Child: Node with a parent
- Leaf: Node with no children
- Subtree: Tree rooted at any node
- Height: Longest path from root to leaf
- Depth: Distance from root to a node
- Level: All nodes at the same depth
Basic Tree Implementation
Generic Tree Node
public class TreeNode<T>
{
public T Value { get; set; }
public List<TreeNode<T>> Children { get; set; }
public TreeNode<T> Parent { get; set; }
public TreeNode(T value)
{
Value = value;
Children = new List<TreeNode<T>>();
Parent = null;
}
public void AddChild(TreeNode<T> child)
{
Children.Add(child);
child.Parent = this;
}
public void RemoveChild(TreeNode<T> child)
{
Children.Remove(child);
child.Parent = null;
}
public bool IsLeaf => Children.Count == 0;
public bool IsRoot => Parent == null;
}
Binary Tree Node
public class BinaryTreeNode<T>
{
public T Value { get; set; }
public BinaryTreeNode<T> Left { get; set; }
public BinaryTreeNode<T> Right { get; set; }
public BinaryTreeNode(T value)
{
Value = value;
Left = null;
Right = null;
}
public bool IsLeaf => Left == null && Right == null;
}
Tree Traversal Algorithms
Depth-First Traversals
Pre-order Traversal (Root → Left → Right)
public static class TreeTraversal
{
public static List<T> PreOrderTraversal<T>(BinaryTreeNode<T> root)
{
var result = new List<T>();
PreOrderHelper(root, result);
return result;
}
private static void PreOrderHelper<T>(BinaryTreeNode<T> node, List<T> result)
{
if (node == null) return;
result.Add(node.Value); // Visit root
PreOrderHelper(node.Left, result); // Traverse left
PreOrderHelper(node.Right, result); // Traverse right
}
// Iterative version
public static List<T> PreOrderIterative<T>(BinaryTreeNode<T> root)
{
var result = new List<T>();
if (root == null) return result;
var stack = new Stack<BinaryTreeNode<T>>();
stack.Push(root);
while (stack.Count > 0)
{
var current = stack.Pop();
result.Add(current.Value);
// Push right first, then left (so left is processed first)
if (current.Right != null) stack.Push(current.Right);
if (current.Left != null) stack.Push(current.Left);
}
return result;
}
}
In-order Traversal (Left → Root → Right)
public static List<T> InOrderTraversal<T>(BinaryTreeNode<T> root)
{
var result = new List<T>();
InOrderHelper(root, result);
return result;
}
private static void InOrderHelper<T>(BinaryTreeNode<T> node, List<T> result)
{
if (node == null) return;
InOrderHelper(node.Left, result); // Traverse left
result.Add(node.Value); // Visit root
InOrderHelper(node.Right, result); // Traverse right
}
// Iterative version
public static List<T> InOrderIterative<T>(BinaryTreeNode<T> root)
{
var result = new List<T>();
var stack = new Stack<BinaryTreeNode<T>>();
var current = root;
while (current != null || stack.Count > 0)
{
// Go to leftmost node
while (current != null)
{
stack.Push(current);
current = current.Left;
}
// Current is null, pop from stack
current = stack.Pop();
result.Add(current.Value);
// Go to right subtree
current = current.Right;
}
return result;
}
Post-order Traversal (Left → Right → Root)
public static List<T> PostOrderTraversal<T>(BinaryTreeNode<T> root)
{
var result = new List<T>();
PostOrderHelper(root, result);
return result;
}
private static void PostOrderHelper<T>(BinaryTreeNode<T> node, List<T> result)
{
if (node == null) return;
PostOrderHelper(node.Left, result); // Traverse left
PostOrderHelper(node.Right, result); // Traverse right
result.Add(node.Value); // Visit root
}
Breadth-First Traversal (Level Order)
public static List<T> LevelOrderTraversal<T>(BinaryTreeNode<T> root)
{
var result = new List<T>();
if (root == null) return result;
var queue = new Queue<BinaryTreeNode<T>>();
queue.Enqueue(root);
while (queue.Count > 0)
{
var current = queue.Dequeue();
result.Add(current.Value);
if (current.Left != null) queue.Enqueue(current.Left);
if (current.Right != null) queue.Enqueue(current.Right);
}
return result;
}
// Level order with level separation
public static List<List<T>> LevelOrderByLevels<T>(BinaryTreeNode<T> root)
{
var result = new List<List<T>>();
if (root == null) return result;
var queue = new Queue<BinaryTreeNode<T>>();
queue.Enqueue(root);
while (queue.Count > 0)
{
int levelSize = queue.Count;
var currentLevel = new List<T>();
for (int i = 0; i < levelSize; i++)
{
var current = queue.Dequeue();
currentLevel.Add(current.Value);
if (current.Left != null) queue.Enqueue(current.Left);
if (current.Right != null) queue.Enqueue(current.Right);
}
result.Add(currentLevel);
}
return result;
}
Binary Search Trees (BST)
BST Implementation
public class BinarySearchTree<T> where T : IComparable<T>
{
private BinaryTreeNode<T> root;
public void Insert(T value)
{
root = InsertHelper(root, value);
}
private BinaryTreeNode<T> InsertHelper(BinaryTreeNode<T> node, T value)
{
if (node == null)
{
return new BinaryTreeNode<T>(value);
}
int comparison = value.CompareTo(node.Value);
if (comparison < 0)
{
node.Left = InsertHelper(node.Left, value);
}
else if (comparison > 0)
{
node.Right = InsertHelper(node.Right, value);
}
// If equal, don't insert (no duplicates)
return node;
}
public bool Search(T value)
{
return SearchHelper(root, value);
}
private bool SearchHelper(BinaryTreeNode<T> node, T value)
{
if (node == null) return false;
int comparison = value.CompareTo(node.Value);
if (comparison == 0) return true;
else if (comparison < 0) return SearchHelper(node.Left, value);
else return SearchHelper(node.Right, value);
}
public void Delete(T value)
{
root = DeleteHelper(root, value);
}
private BinaryTreeNode<T> DeleteHelper(BinaryTreeNode<T> node, T value)
{
if (node == null) return null;
int comparison = value.CompareTo(node.Value);
if (comparison < 0)
{
node.Left = DeleteHelper(node.Left, value);
}
else if (comparison > 0)
{
node.Right = DeleteHelper(node.Right, value);
}
else
{
// Node to delete found
if (node.Left == null) return node.Right;
if (node.Right == null) return node.Left;
// Node has two children - find inorder successor
node.Value = FindMin(node.Right);
node.Right = DeleteHelper(node.Right, node.Value);
}
return node;
}
private T FindMin(BinaryTreeNode<T> node)
{
while (node.Left != null)
{
node = node.Left;
}
return node.Value;
}
public List<T> GetSortedValues()
{
return TreeTraversal.InOrderTraversal(root);
}
}
Common Tree Algorithms
Tree Properties
public static class TreeAlgorithms
{
public static int GetHeight<T>(BinaryTreeNode<T> root)
{
if (root == null) return -1;
return 1 + Math.Max(GetHeight(root.Left), GetHeight(root.Right));
}
public static int CountNodes<T>(BinaryTreeNode<T> root)
{
if (root == null) return 0;
return 1 + CountNodes(root.Left) + CountNodes(root.Right);
}
public static bool IsBalanced<T>(BinaryTreeNode<T> root)
{
return CheckBalance(root) != -1;
}
private static int CheckBalance<T>(BinaryTreeNode<T> node)
{
if (node == null) return 0;
int leftHeight = CheckBalance(node.Left);
if (leftHeight == -1) return -1;
int rightHeight = CheckBalance(node.Right);
if (rightHeight == -1) return -1;
if (Math.Abs(leftHeight - rightHeight) > 1) return -1;
return Math.Max(leftHeight, rightHeight) + 1;
}
public static bool IsValidBST<T>(BinaryTreeNode<T> root) where T : IComparable<T>
{
return ValidateBST(root, default(T), default(T), false, false);
}
private static bool ValidateBST<T>(BinaryTreeNode<T> node, T min, T max,
bool hasMin, bool hasMax) where T : IComparable<T>
{
if (node == null) return true;
if ((hasMin && node.Value.CompareTo(min) <= 0) ||
(hasMax && node.Value.CompareTo(max) >= 0))
{
return false;
}
return ValidateBST(node.Left, min, node.Value, hasMin, true) &&
ValidateBST(node.Right, node.Value, max, true, hasMax);
}
}
Finding Paths
public static bool HasPath<T>(BinaryTreeNode<T> root, T target) where T : IEquatable<T>
{
if (root == null) return false;
if (root.Value.Equals(target)) return true;
return HasPath(root.Left, target) || HasPath(root.Right, target);
}
public static List<T> FindPath<T>(BinaryTreeNode<T> root, T target) where T : IEquatable<T>
{
var path = new List<T>();
if (FindPathHelper(root, target, path))
{
return path;
}
return new List<T>(); // No path found
}
private static bool FindPathHelper<T>(BinaryTreeNode<T> node, T target, List<T> path) where T : IEquatable<T>
{
if (node == null) return false;
path.Add(node.Value);
if (node.Value.Equals(target)) return true;
if (FindPathHelper(node.Left, target, path) || FindPathHelper(node.Right, target, path))
{
return true;
}
// Backtrack
path.RemoveAt(path.Count - 1);
return false;
}
Tree Applications
Expression Trees
public class ExpressionTree
{
public class ExpressionNode
{
public string Value { get; set; }
public ExpressionNode Left { get; set; }
public ExpressionNode Right { get; set; }
public bool IsOperator => "+-*/".Contains(Value);
public ExpressionNode(string value)
{
Value = value;
}
}
public static double EvaluateExpression(ExpressionNode root)
{
if (root == null) return 0;
if (!root.IsOperator)
{
return double.Parse(root.Value);
}
double leftValue = EvaluateExpression(root.Left);
double rightValue = EvaluateExpression(root.Right);
return root.Value switch
{
"+" => leftValue + rightValue,
"-" => leftValue - rightValue,
"*" => leftValue * rightValue,
"/" => leftValue / rightValue,
_ => throw new InvalidOperationException($"Unknown operator: {root.Value}")
};
}
// Convert expression tree to infix notation
public static string ToInfixNotation(ExpressionNode root)
{
if (root == null) return "";
if (!root.IsOperator)
{
return root.Value;
}
string left = ToInfixNotation(root.Left);
string right = ToInfixNotation(root.Right);
return $"({left} {root.Value} {right})";
}
}
File System Tree
public class FileSystemNode
{
public string Name { get; set; }
public bool IsDirectory { get; set; }
public List<FileSystemNode> Children { get; set; }
public long Size { get; set; }
public FileSystemNode(string name, bool isDirectory = false)
{
Name = name;
IsDirectory = isDirectory;
Children = isDirectory ? new List<FileSystemNode>() : null;
Size = 0;
}
public void AddChild(FileSystemNode child)
{
if (IsDirectory)
{
Children.Add(child);
}
}
public long GetTotalSize()
{
if (!IsDirectory) return Size;
long totalSize = 0;
foreach (var child in Children)
{
totalSize += child.GetTotalSize();
}
return totalSize;
}
public void PrintStructure(int depth = 0)
{
string indent = new string(' ', depth * 2);
string type = IsDirectory ? "[DIR]" : "[FILE]";
Console.WriteLine($"{indent}{type} {Name}");
if (IsDirectory)
{
foreach (var child in Children)
{
child.PrintStructure(depth + 1);
}
}
}
}
Time Complexity Summary
Operation | BST Average | BST Worst | Balanced Tree |
---|---|---|---|
Search | O(log n) | O(n) | O(log n) |
Insert | O(log n) | O(n) | O(log n) |
Delete | O(log n) | O(n) | O(log n) |
Traversal | O(n) | O(n) | O(n) |
Space Complexity: O(n) for all tree operations
When to Use Trees
Use trees when:
- Data has natural hierarchy
- Need efficient search in sorted data
- Building decision systems
- Parsing expressions or syntax
- Managing hierarchical relationships
Common applications:
- File systems
- Database indexing
- Compiler design (syntax trees)
- Decision trees in AI
- Organizational structures
- Game trees for AI
Avoid trees when:
- Data doesn’t have hierarchy
- Simple array/list operations suffice
- Need constant time operations
- Working with fixed-size collections
Quick Reference
Traversal Summary
| Traversal | Order | Use Case | |———–|——-|———-| | Pre-order | Root → Left → Right | Copy tree, prefix notation | | In-order | Left → Root → Right | BST sorted output | | Post-order | Left → Right → Root | Delete tree, postfix notation | | Level-order | Level by level | Find shortest path, serialize |
Time Complexity
| Operation | Binary Tree | BST (balanced) | |———–|————-|—————-| | Search | O(n) | O(log n) | | Insert | O(n) | O(log n) | | Delete | O(n) | O(log n) | | Traversal | O(n) | O(n) |
Found this guide helpful? Share it with your team:
Share on LinkedIn