-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
213: Implementing Data Structures from Scratch: Trees and Graphs
How do I actually structure a tree node without creating a memory leak nightmare?
When you first start building trees, the instinct is to use raw pointers. But in a real C++ project, raw new and delete calls are a recipe for disaster, especially when you have a recursive structure where one missing delete in a destructor cascades into a massive leak.
I always recommend std::unique_ptr for tree nodes. Since a parent "owns" its children, unique_ptr perfectly models this relationship. When the root is destroyed, it triggers a chain reaction that cleans up the entire tree automatically. Here is how I'd set up a basic node for something like a company org chart:
#include <memory>
#include <string>
#include <vector>
struct Employee {
std::string name;
// Using a vector of unique_ptrs allows for an N-ary tree (multiple subordinates)
std::vector<std::unique_ptr<Employee>> subordinates;
Employee(std::string n) : name(n) {}
void addSubordinate(std::unique_ptr<Employee> emp) {
subordinates.push_back(std::move(emp));
}
};
Notice the std::move. Since unique_ptr cannot be copied, you have to explicitly transfer ownership of the employee node into the subordinates list. It feels a bit clunky at first, but it forces you to be honest about who owns what memory.
What is the most efficient way to represent a graph in C++?
You'll see two main options: Adjacency Matrices and Adjacency Lists. If you're building a dense graph (where almost every node connects to every other node), a matrix (a 2D array) is fine. But in the real world—think of a map of city intersections or a social network—graphs are "sparse." Using a matrix for a city map would waste gigabytes of RAM on zeros.
I almost always go with an Adjacency List. I personally prefer using a std::unordered_map where the key is the node ID and the value is a std::vector of neighbors. This gives you O(1) lookup to find a node's neighbors while keeping the memory footprint lean.
#include <unordered_map>
#include <vector>
#include <string>
class CityMap {
// Maps a city name to a list of cities it's connected to
std::unordered_map<std::string, std::vector<std::string>> adjList;
public:
void addRoad(const std::string& u, const std::string& v) {
adjList[u].push_back(v);
adjList[v].push_back(u); // Assuming roads are bidirectional
}
const std::vector<std::string>& getConnections(const std::string& city) {
return adjList[city];
}
};
How do I stop a graph traversal from running in an infinite loop?
Unlike trees, graphs can have cycles. If you write a simple recursive Depth-First Search (DFS) and your graph has a loop (A → B → C → A), your program will just keep spinning until the stack overflows. It's a classic mistake.
The fix is a "visited" set. You need to keep track of every node you've already touched. I use a std::unordered_set for this because it tells me in constant time if I've been to a city already. Here is a concise way to implement a DFS that actually terminates:
#include <iostream>
#include <unordered_set>
#include <string>
void exploreCity(const std::string& current,
CityMap& map,
std::unordered_set<std::string>& visited) {
// Base case: if we've already been here, turn back
if (visited.find(current) != visited.end()) return;
std::cout < Reproductive "Visiting: " < current < "\n";
visited.insert(current); // Mark as visited
for (const auto& neighbor : map.getConnections(current)) {
exploreCity(neighbor, map, visited);
}
}
By passing the visited set by reference, every recursive call shares the same record of where the traversal has been. If you accidentally pass it by value, you'll find your code still looping because every function call gets its own fresh, empty set.
📋 Practical Task
Exercise: Build a Package Dependency Resolver
In software engineering, build systems (like Make or CMake) use a Directed Acyclic Graph (DAG) to determine the order in which libraries should be compiled. If Library A depends on Library B, B must be compiled first.
Your Task: Implement a DependencyGraph class that handles the following:
- A method
addDependency(string package, string dependsOn)to build the graph. - A method
resolveOrder(string targetPackage)that performs a Depth-First Search to print the correct compilation order.
Requirements:
1. Use a std::unordered_map<std::string, std::vector<std::string>> for the adjacency list.
2. Ensure that a package is only printed after all of its dependencies have been printed (this is essentially a Topological Sort).
3. Use a std::unordered_set to track visited nodes to avoid redundant processing.
Test Case:
If you add dependencies: ("Engine", "Core"), ("Engine", "Renderer"), ("Renderer", "Core"), ("App", "Engine").
Resolving "App" should output a valid order, such as: Core, Renderer, Engine, App.
There are no comments for now.