C++
Completed
-
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
164: Graph Traversal: BFS and DFS in C++
By now, you've probably handled linear data structures, but graphs are a different beast because they don't have a natural "start" or "end." When we talk about traversal, we're really just talking about how we decide which node to visit next. I've found that the best way to wrap your head around this is to stop thinking about "algorithms" and start thinking about "search patterns."
How do I actually choose between BFS and DFS?
It really comes down to what you're looking for. I like to use the "ripple" analogy for Breadth-First Search (BFS). Imagine dropping a stone in a pond; the wave expands outward in concentric circles. BFS visits every neighbor at distance 1, then every neighbor at distance 2, and so on. If you need to find the shortest path in an unweighted graph (like the fewest number of hops between two people on LinkedIn), BFS is your only real choice.
Depth-First Search (DFS), on the other hand, is like exploring a dark cave. You pick a path and follow it as far as it goes until you hit a dead end, then you backtrack to the last fork in the road. DFS is generally more memory-efficient for deep graphs and is the go-to for things like solving a maze or detecting cycles in a dependency graph. If you just need to know "can I get from A to B?" and don't care how long it takes, DFS is often simpler to write.
How do I prevent my code from getting stuck in an infinite loop?
This is the most common mistake I see. Unlike a tree, a graph can have cycles. If Node A points to Node B, and Node B points back to Node A, a naive traversal will bounce between them until your program crashes with a stack overflow.
The solution is a visited tracker. I usually use a std::vector<bool> if the nodes are indexed numerically, or a std::unordered_set if the nodes are strings or non-sequential IDs. You mark a node as visited the moment you encounter it, and you simply ignore any node that's already been flagged. It's a simple check, but if you forget it, your code is a ticking time bomb.
What does a clean implementation look like in C++?
I prefer using an adjacency list (a vector of vectors) because it's far more efficient than a matrix for most real-world graphs. Here is how I would implement both patterns for a simple network of connected computers.
#include <iostream>
#include <vector>
#include <queue>
#include <stack>
using namespace std;
class NetworkGraph {
int nodes;
vector<vector<int>> adj;
public:
NetworkGraph(int n) : nodes(n), adj(n) {}
void addEdge(int u, int v) {
adj[u].push_back(v);
adj[v].push_back(u); // Undirected graph
}
void bfs(int startNode) {
vector<bool> visited(nodes, false);
queue<int> q;
visited[startNode] = true;
q.push(startNode);
cout << "BFS Order: ";
while (!q.empty()) {
int curr = q.front();
q.pop();
cout << curr << " ";
for (int neighbor : adj[curr]) {
if (!visited[neighbor]) {
visited[neighbor] = true;
q.push(neighbor);
}
}
}
cout << endl;
}
void dfs(int node, vector<bool>& visited) {
visited[node] = true;
cout << node << " ";
for (int neighbor : adj[node]) {
if (!visited[neighbor]) {
dfs(neighbor, visited);
}
}
}
void startDfs(int startNode) {
vector<bool> visited(nodes, false);
cout << "DFS Order: ";
dfs(startNode, visited);
cout << endl;
}
};
int main() {
NetworkGraph net(6);
net.addEdge(0, 1);
net.addEdge(0, 2);
net.addEdge(1, 3);
net.addEdge(1, 4);
net.addEdge(2, 5);
net.bfs(0); // Expected: 0 1 2 3 4 5
net.startDfs(0); // Expected: 0 1 3 4 2 5 (or similar)
return 0;
}
Notice that BFS uses a std::queue to keep track of the "frontier," while DFS uses recursion, which implicitly uses the call stack. If you're dealing with a massive graph where recursion might hit the limit, you can easily rewrite DFS using a std::stack explicitly.
📋 Practical Task
Exercise: Building a Social Circle Distance Tracker
You are tasked with building a simplified "Degrees of Separation" tool. You will be given a graph representing people (nodes) and their friendships (edges). Your goal is to determine the minimum number of hops between two specific people.
Requirements:
- Implement a function
int getShortestDistance(int start, int target, const vector<vector<int>>& adj). - Use a Breadth-First Search (BFS) approach, as DFS will not guarantee the shortest path.
- Instead of just tracking if a node was visited, maintain a
distancevector wheredistance[i]stores the number of hops from the start node to nodei. - If the target is unreachable, return -1.
Test Case:
Graph: 0-1, 0-2, 1-3, 2-3, 3-4.
Start: 0, Target: 4.
Expected Output: 3 (Path: 0 → 1 → 3 → 4 or 0 → 2 → 3 → 4).
There are no comments for now.