Graphs
Graph Representation
A graph is a set of nodes (also called vertices) connected by edges. It's the most general data structure you'll meet in this curriculum — a tree (Module 7) is
Jr Codex DSA Notes
Level: Advanced Prerequisites: Module 8, Chapter 4 Time to complete: ~25 minutes
Table of Contents
- What Is a Graph?
- Directed vs. Undirected
- Weighted vs. Unweighted
- Adjacency List
- Adjacency Matrix
- Choosing a Representation
- Summary & Next Steps
1. What Is a Graph?
A graph is a set of nodes (also called vertices) connected by edges. It's the most general data structure you'll meet in this curriculum — a tree (Module 7) is a graph with no cycles and exactly one path between any two nodes; a linked list (Module 6) is a graph where every node connects to at most one other.
A --- B
| |
C --- D --- E
Nodes: {A, B, C, D, E}
Edges: {(A,B), (A,C), (B,D), (C,D), (D,E)}
2. Directed vs. Undirected
- Undirected — an edge
(A, B)means you can travel bothA → BandB → A(the diagram above is undirected — a friendship graph is a typical example). - Directed — an edge
A → Bonly lets you travel fromAtoB, not the reverse (a "follows" relationship on social media, or a prerequisite chain, are typical examples — the latter is exactly what Chapter 4's topological sort operates on).
3. Weighted vs. Unweighted
- Unweighted — every edge is equally "costly" to traverse. BFS (Chapter 2) finds shortest paths here by counting edges.
- Weighted — each edge has a cost (distance, time, price). Finding shortest paths in a weighted graph needs an algorithm like Dijkstra's, which builds on the priority queue from Module 7 Chapter 4 — out of scope for this introductory module, but worth knowing the term exists.
This module focuses on unweighted graphs, both directed and undirected, since they cover the large majority of interview-level graph questions.
4. Adjacency List
The dominant representation in interview code: a dict mapping each node to a list of its neighbors — this is a direct, natural application of Module 8's hash maps.
graph = {
"A": ["B", "C"],
"B": ["A", "D"],
"C": ["A", "D"],
"D": ["B", "C", "E"],
"E": ["D"],
}
print(graph["D"]) # ['B', 'C', 'E'] — O(1) average to find D's neighbor listSpace complexity: O(V + E) — one entry per node (V = number of vertices) plus one entry per edge (E), since each edge appears in exactly one (directed) or two (undirected) neighbor lists.
Building it from a raw edge list:
def build_adjacency_list(edges, directed=False):
graph = {}
for a, b in edges:
graph.setdefault(a, []).append(b)
if not directed:
graph.setdefault(b, []).append(a)
return graph
edges = [("A", "B"), ("A", "C"), ("B", "D"), ("C", "D"), ("D", "E")]
print(build_adjacency_list(edges))5. Adjacency Matrix
A 2D array where matrix[i][j] = 1 (or the edge weight) if an edge exists from node i to node j, else 0.
# A B C D E
# A [ 0, 1, 1, 0, 0 ]
# B [ 1, 0, 0, 1, 0 ]
# C [ 1, 0, 0, 1, 0 ]
# D [ 0, 1, 1, 0, 1 ]
# E [ 0, 0, 0, 1, 0 ]
matrix = [
[0, 1, 1, 0, 0],
[1, 0, 0, 1, 0],
[1, 0, 0, 1, 0],
[0, 1, 1, 0, 1],
[0, 0, 0, 1, 0],
]
# Checking whether an edge exists between node 0 and node 1:
print(matrix[0][1]) # O(1) — direct indexSpace complexity: O(V²) — a full grid regardless of how many edges actually exist. Checking whether a specific edge exists is O(1), but finding all neighbors of a node requires scanning an entire row — O(V), even if that node has only one neighbor.
6. Choosing a Representation
| Adjacency List | Adjacency Matrix | |
|---|---|---|
| Space | O(V + E) — efficient for sparse graphs | O(V²) — wasteful for sparse graphs |
| Check if edge (a, b) exists | O(degree of a) | O(1) |
| Find all neighbors of a node | O(degree of a) | O(V) |
| Best for | Most real-world and interview graphs (sparse: E much less than V²) | Dense graphs, or when frequent edge-existence checks dominate |
Default choice: use an adjacency list (a dict of lists) unless you have a specific reason to need O(1) edge-existence checks on a dense graph — it's what every algorithm in the rest of this module assumes.
7. Summary & Next Steps
Key Takeaways
- A graph is nodes (vertices) plus edges connecting them; trees and linked lists are both special-case graphs.
- Directed edges are one-way; weighted edges carry a cost — this module focuses on unweighted directed/undirected graphs.
- Adjacency list (
O(V + E)space) is the standard choice for sparse graphs and is what BFS/DFS in this module are built on; adjacency matrix (O(V²)space) trades memory forO(1)edge-existence checks.
Concept Check
- Why is a tree considered a special case of a graph?
- What's the space complexity difference between an adjacency list and an adjacency matrix, and when does that difference matter?
- Given an adjacency list
dict, what's the complexity of finding all neighbors of a given node?
Next Chapter
→ Chapter 2: Breadth-First Search
Jr Codex — 1-on-1 Personalized Coaching | Back to Module Index