C++ Tree Data Structures
Study Guide
📖 Core Concepts
Tree Fundamentals A tree is a non-linear data structure that organizes information hierarchically, starting from a single 'root' node and branching out, ideal for representing relationships.
C++ Node Structure
In C++, a tree node is a struct or class containing a data value and self-referential pointers that hold the memory addresses of its children nodes.
Building a Tree
Trees are built in C++ by dynamically allocating nodes on the heap with new and manually linking them by assigning child node addresses to parent pointers.
Tree Traversal (Pre-order) Traversal is the process of visiting every node in a tree; pre-order traversal follows a "visit-left-right" pattern, using recursion to navigate down each branch.
Searching a Tree A recursive function can search a tree by checking the current node's value and, if it's not a match, calling itself on the left and right subtrees.
Deleting a Tree To prevent memory leaks, dynamically allocated tree nodes must be destroyed using a post-order traversal to safely delete children before their parent.
📌 Must Remember
Tree Fundamentals
- Hierarchical vs. Linear: Unlike arrays or lists (linear), trees represent parent-child relationships, perfect for file systems or org charts.
- The Root: Every non-empty tree has exactly one root node, which is the single entry point to the structure.
- Nodes and Edges: Nodes hold data, and edges are the connections (pointers) between a parent and its children.
- Parent and Child: Any node with a connection leading to it is a child. The node it connects from is the parent.
- Leaf Nodes: Nodes with no children are called leaves; they are the endpoints of any path.
- Subtree: Any node and all its descendants form a subtree, which is itself a complete tree.
C++ Node Structure
structorclass: A node is defined as astructorclassin C++.- Data Member: The node structure must contain a variable to hold its own data (e.g.,
int data;). - Self-Referential Pointers: The key feature is pointers of the same
structtype (e.g.,Node* left;), allowing it to point to other nodes. - Binary Tree Constraint: A binary tree node has at most two children, typically named
leftandright. - Blueprint: The node definition is just a blueprint; it doesn't create any nodes until you use
new.
Building a Tree
- Dynamic Allocation: Nodes must be created on the heap using the
newkeyword (e.g.,Node* root = new Node();). - Heap Memory: Using
newallocates memory that persists beyond the function's scope, which is necessary for a tree. - Arrow Operator
->: To access members of a struct through a pointer, you must use the arrow operator (->), not the dot operator (.). nullptr: A pointer that doesn't point to anything should be initialized tonullptrto signify the absence of a node (e.g., a missing child).- Manual Wiring: You connect nodes by assigning the memory address of one node to the pointer of another (e.g.,
root->left = child1;).
Tree Traversal (Pre-order)
- Traversal Goal: The purpose of traversal is to systematically visit every node in the tree exactly once.
- Recursion is Key: Tree traversal is naturally recursive; a function calls itself on a node's children.
- Base Case: The recursion must have a base case to stop:
if (node == nullptr) return;. This prevents infinite loops and crashes. - Pre-order Sequence: The order is always: 1) Process the current node, 2) Traverse the left subtree, 3) Traverse the right subtree.
- Call Stack: Each recursive call adds a new frame to the program's call stack, which keeps track of where to return.
Searching a Tree
- Boolean Return: A search function typically returns
trueif the value is found andfalseotherwise. - Three Checks: In each recursive call, you check three things: 1) Is the current node null? (return false), 2) Is the current node the target? (return true), 3) If not, search left and right.
- Divide and Conquer: The search works by breaking the problem down: if the value isn't here, it might be in the left or right half (subtree).
- Recursive Search Call: The logic is
return search(node->left, value) || search(node->right, value);. - Graceful Failure: The search ends gracefully when it reaches a
nullptr, which acts as the base case for a failed path.
Deleting a Tree
- Memory Leaks: Forgetting to
deletememory allocated withnewcauses a memory leak, where your program loses memory it can no longer access. deleteKeyword: Thedeletekeyword frees up the memory that a pointer points to, making it available for other parts of the program.- Post-order Deletion is Mandatory: You must delete a node's children before deleting the node itself. Otherwise, you lose the pointers to the children and can't delete them.
- Post-order Sequence: The order is: 1) Traverse and delete the left subtree, 2) Traverse and delete the right subtree, 3) Delete the current node.
- Dangling Pointers: After deleting a node, set the pointer that pointed to it to
nullptrto avoid it becoming a 'dangling pointer'.
📚 Key Terms
Node A fundamental unit of a tree that contains a data value and pointers to its child nodes.
- Used in context: We created a new
Nodeon the heap and set itsdatafield to 10. - Topic: Tree Fundamentals
Root The single, top-most node in a tree from which all other nodes descend; it has no parent.
- Used in context: The search algorithm always begins at the
rootof the tree. - Topic: Tree Fundamentals
Edge The conceptual link between a parent node and a child node, represented in C++ by a pointer.
- Used in context: An
edgeconnects the root to its left child, showing their relationship. - Topic: Tree Fundamentals
Leaf Node A node that has no children; it is at the end of a branch.
- Used in context: When our traversal hits a
leaf node, its left and right pointers will both benullptr. - Topic: Tree Fundamentals
Parent / Child A relationship where a parent node has pointers pointing to its child nodes.
- Used in context: The root node is the
parentof two other nodes, which are itschildnodes. - Topic: Tree Fundamentals
Binary Tree A specific type of tree where each node can have at most two children, a 'left' child and a 'right' child.
- Used in context: Our C++
Nodestruct is designed for aBinary Treebecause it only hasleftandrightpointers. - Topic: C++ Node Structure
new
A C++ keyword used for dynamic memory allocation, which reserves memory on the heap for an object.
- Used in context: We used
new Node()to create a node that will persist after the function finishes. - Topic: Building a Tree
delete
A C++ keyword used to deallocate memory that was previously allocated with new, preventing memory leaks.
- Used in context: After traversing the entire left subtree, we call
deleteon the left child's parent. - Topic: Deleting a Tree
nullptr
A C++ keyword representing a null pointer, used to indicate that a pointer does not point to any valid memory address.
- Used in context: A leaf node's child pointers are set to
nullptrto show it has no children. - Topic: Building a Tree
Pointer A variable that stores the memory address of another variable.
- Used in context: The
rootpointer holds the memory address of the first node we created on the heap. - Topic: C++ Node Structure
Traversal The process of visiting each node in a tree data structure in a systematic order.
- Used in context: We implemented a pre-order
traversalfunction to print every value in the tree. - Topic: Tree Traversal (Pre-order)
Recursion A programming technique where a function calls itself in order to solve a problem by breaking it into smaller, self-similar subproblems.
- Used in context: Tree traversal is elegantly solved with
recursion, where the traversal function calls itself for the left and right children. - Topic: Tree Traversal (Pre-order)
Base Case The condition in a recursive function that stops the recursion and prevents an infinite loop.
- Used in context: The
base casefor our traversal isif (node == nullptr), which stops the function from trying to access a non-existent node. - Topic: Tree Traversal (Pre-order)
Memory Leak A resource management error where a program allocates memory but loses the ability to free it, consuming resources unnecessarily.
- Used in context: If we don't
deleteevery node we create withnew, our program will have amemory leak. - Topic: Deleting a Tree
Pre-order Traversal A traversal algorithm that follows the order: 1. Visit the current node, 2. Traverse the left subtree, 3. Traverse the right subtree.
- Used in context: A
Pre-order Traversalis useful for creating a copy of a tree. - Topic: Tree Traversal (Pre-order)
Post-order Traversal A traversal algorithm that follows the order: 1. Traverse the left subtree, 2. Traverse the right subtree, 3. Visit the current node.
- Used in context: We must use
Post-order Traversalto safely delete a tree, as it ensures children are deleted before their parent. - Topic: Deleting a Tree
🔍 Key Comparisons
Linear vs. Hierarchical Data Structures
| Feature | Linear Structures (Array, List) | Hierarchical Structures (Tree) |
|---|---|---|
| Organization | Sequential, one-after-the-other. | Parent-child, top-down branching. |
| Relationships | Each element has at most one before and one after. | Each element (node) can have multiple children. |
| Access | Often sequential (e.g., iterating) or direct by index. | Starts at the root and follows paths (edges). |
| Use Case | Storing a simple sequence of items. | Representing file systems, org charts, DOM. |
Memory trick: A List is like a Line. A Tree looks like a Tree.
Topic: Tree Fundamentals
Pre-order vs. Post-order Traversal
| Feature | Pre-order Traversal | Post-order Traversal |
|---|---|---|
| Order | Visit -> Left -> Right | Left -> Right -> Visit |
| Parent Processing | The parent node is processed before its children. | The parent node is processed after all its children. |
| Primary Use Case | Copying a tree, printing nodes in a top-down fashion. | Safely deleting a tree, evaluating expression trees. |
| Example Output | Root is printed first. | Leaf nodes are printed first, root is last. |
Memory trick: Pre-order processes the parent pre-emptively (first). Post-order processes the parent post-mortem (last).
Topic: Deleting a Tree
⚠️ Common Mistakes
❌ MISTAKE: Accessing a node's data with the dot operator (root.data).
- Why it happens: This is the correct syntax for objects, but not for pointers to objects.
- ✅ Instead: Use the arrow operator
->to access members via a pointer:root->data. - Memory aid: The arrow
->looks like it's 'pointing' to the member. - Topic: Building a Tree
❌ MISTAKE: Forgetting the base case in a recursive traversal function.
- Why it happens: It's easy to focus on the recursive step and forget the condition that stops it.
- ✅ Instead: Always start a recursive tree function with
if (node == nullptr) { return; }. This handles empty trees and leaf nodes correctly. - Topic: Tree Traversal (Pre-order)
❌ MISTAKE: Deleting a parent node before its children.
- Why it happens: A simple pre-order traversal might seem like a logical way to delete, but it orphans the children.
- ✅ Instead: Always use a post-order traversal for deletion. This guarantees you delete from the leaves up to the root, never losing access to any node.
- Topic: Deleting a Tree
❌ MISTAKE: Writing a search function that only checks the left or right side, not both.
- Why it happens: The logic can be tricky. A common error is
if (search(left)) return true; else return search(right);, which incorrectly returnsfalseif the value is on the right side but not the left. - ✅ Instead: Use the logical OR operator to combine the results:
return search(node->left, value) || search(node->right, value);. - Topic: Searching a Tree
❌ MISTAKE: Assuming a new node's pointers are automatically nullptr.
- Why it happens: When you allocate memory with
new, the contents are uninitialized and contain garbage values, notnullptr. - ✅ Instead: After creating a node, always explicitly initialize its pointers:
newNode->left = nullptr;andnewNode->right = nullptr;. - Topic: Building a Tree
🔄 Key Processes
Pre-order Traversal Process
Goal: Print the value of every node in a tree, starting from the root.
Step 1: Define the Base Case
- What happens: Check if the current node pointer is
nullptr. If it is, the function immediately returns, stopping this path of recursion. - Key indicator:
if (node == nullptr) return;
Step 2: Process the Current Node
- What happens: Perform the 'visit' action on the current node. This is often printing its value, but could be any operation.
- Key indicator:
cout << node->data << endl;
Step 3: Recurse on the Left Child
- What happens: Call the same traversal function, passing the current node's
leftchild pointer as the new argument. - Key indicator:
traverse(node->left);
Step 4: Recurse on the Right Child
- What happens: Call the same traversal function again, this time passing the current node's
rightchild pointer. - Key indicator:
traverse(node->right);
Topic: Tree Traversal (Pre-order)
Recursive Search Process
Goal: Determine if a specific value exists anywhere in the tree.
Step 1: Base Case - Not Found
- What happens: Check if the current node is
nullptr. If so, we've reached the end of a branch without finding the value. Returnfalse. - Key indicator:
if (node == nullptr) return false;
Step 2: Base Case - Found
- What happens: Check if the current node's data matches the target value. If so, the search is successful. Return
true. - Key indicator:
if (node->data == targetValue) return true;
Step 3: Recurse and Combine Results
- What happens: If the value hasn't been found yet, recursively call the search function on both the left and right children. Return
trueif either of these calls returnstrue. - Key indicator:
return search(node->left, targetValue) || search(node->right, targetValue);
Topic: Searching a Tree
Post-order Deletion Process
Goal: Safely delete every node in the tree to prevent memory leaks.
Step 1: Define the Base Case
- What happens: Check if the current node pointer is
nullptr. If it is, there is nothing to delete, so the function returns. - Key indicator:
if (node == nullptr) return;
Step 2: Recurse and Delete Left Subtree
- What happens: Call the same deletion function on the current node's
leftchild. This ensures the entire left subtree is deleted before proceeding. - Key indicator:
deleteTree(node->left);
Step 3: Recurse and Delete Right Subtree
- What happens: Call the same deletion function on the current node's
rightchild. - Key indicator:
deleteTree(node->right);
Step 4: Delete the Current Node
- What happens: Now that both children (and all their descendants) have been deleted, it is safe to delete the current node.
- Key indicator:
delete node;
Topic: Deleting a Tree
📝 Worked Examples
#include <iostream>
// The blueprint for our tree nodes
struct Node {
int data;
Node* left;
Node* right;
};
// Function to print the tree using Pre-order traversal
void printTreePreOrder(Node* node) {
// Base case: stop if the node is null
if (node == nullptr) {
return;
}
// 1. Visit the current node
std::cout << node->data << " ";
// 2. Traverse left subtree
printTreePreOrder(node->left);
// 3. Traverse right subtree
printTreePreOrder(node->right);
}
// Function to delete the entire tree using Post-order traversal
void deleteTree(Node* node) {
// Base case: stop if the node is null
if (node == nullptr) {
return;
}
// 1. Traverse and delete left subtree
deleteTree(node->left);
// 2. Traverse and delete right subtree
deleteTree(node->right);
// 3. Delete the current node
std::cout << "Deleting node: " << node->data << std::endl;
delete node;
}
int main() {
// --- Part 1: Building the Tree ---
// Create the root node on the heap
Node* root = new Node();
root->data = 10;
root->left = nullptr;
root->right = nullptr;
// Create a left child
Node* leftChild = new Node();
leftChild->data = 5;
leftChild->left = nullptr;
leftChild->right = nullptr;
// Create a right child
Node* rightChild = new Node();
rightChild->data = 15;
rightChild->left = nullptr;
rightChild->right = nullptr;
// Manually link the children to the root
root->left = leftChild;
root->right = rightChild;
// --- Part 2: Traversing the Tree ---
std::cout << "Tree values (Pre-order): ";
printTreePreOrder(root);
std::cout << std::endl << std::endl;
// --- Part 3: Deleting the Tree ---
deleteTree(root);
// Set the original pointer to null to avoid a dangling pointer
root = nullptr;
return 0;
}
/*
Expected Output:
Tree values (Pre-order): 10 5 15
Deleting node: 5
Deleting node: 15
Deleting node: 10
*/