Graph Concepts and Terminology
What is a Graph?
A graph G = (V, E) consists of a set of vertices V and a set of edges E connecting pairs of vertices. Unlike trees, graphs can contain cycles and have more flexible connectivity patterns.
Key Terminology
| Term | Definition |
|---|---|
| Vertex (Node) | A fundamental unit of a graph |
| Edge | A connection between two vertices |
| Directed Edge | An edge with a direction (u → v) |
| Undirected Edge | An edge with no direction (u — v) |
| Weighted Edge | An edge with an associated cost/distance |
| Degree | Number of edges incident to a vertex |
| In-degree | Number of incoming edges (directed graphs) |
| Out-degree | Number of outgoing edges (directed graphs) |
| Path | A sequence of vertices connected by edges |
| Cycle | A path that starts and ends at the same vertex |
Graph Types
Undirected Graph: Directed Graph:
A --- B A → B
| / | ↓ ↓
| / | C → D
C --- D
Weighted Graph: Bipartite Graph:
A —5— B Set X: {A, C}
| / Set Y: {B, D}
3 2 A—B, A—D, C—B, C—D
| / (edges only between sets)
C —1— D
When to Use Graphs
- Social networks: Users as vertices, friendships as edges
- Road maps: Cities as vertices, roads as weighted edges
- Dependencies: Tasks as vertices, prerequisites as directed edges
- Web crawling: Pages as vertices, hyperlinks as directed edges
Graph Representation in Java
Adjacency List
Best for sparse graphs. Uses a map or array of lists.
import java.util.*;
public class Graph {
private int vertices;
private Map<Integer, List<Integer>> adjList;
public Graph(int vertices) {
this.vertices = vertices;
this.adjList = new HashMap<>();
for (int i = 0; i < vertices; i++) {
adjList.put(i, new ArrayList<>());
}
}
// Add edge for undirected graph
public void addEdge(int u, int v) {
adjList.get(u).add(v);
adjList.get(v).add(u);
}
// Add edge for directed graph
public void addDirectedEdge(int u, int v) {
adjList.get(u).add(v);
}
// Add weighted edge
// Use Map<Integer, List<int[]>> where int[] = {neighbor, weight}
public List<Integer> getNeighbors(int vertex) {
return adjList.getOrDefault(vertex, new ArrayList<>());
}
public int getVertices() {
return vertices;
}
}
Time Complexity:
- Add edge: O(1)
- Remove edge: O(degree)
- Check if edge exists: O(degree)
- Get all neighbors: O(1)
Adjacency Matrix
Best for dense graphs. Uses a 2D array.
public class GraphMatrix {
private int vertices;
private int[][] matrix;
public GraphMatrix(int vertices) {
this.vertices = vertices;
this.matrix = new int[vertices][vertices];
}
public void addEdge(int u, int v) {
matrix[u][v] = 1;
matrix[v][u] = 1; // Remove for directed graph
}
public void addWeightedEdge(int u, int v, int weight) {
matrix[u][v] = weight;
matrix[v][u] = weight; // Remove for directed graph
}
public boolean hasEdge(int u, int v) {
return matrix[u][v] != 0;
}
public List<Integer> getNeighbors(int vertex) {
List<Integer> neighbors = new ArrayList<>();
for (int i = 0; i < vertices; i++) {
if (matrix[vertex][i] != 0) {
neighbors.add(i);
}
}
return neighbors;
}
}
Time Complexity:
- Add edge: O(1)
- Remove edge: O(1)
- Check if edge exists: O(1)
- Get all neighbors: O(V)
Choosing the Right Representation
| Scenario | Adjacency List | Adjacency Matrix |
|---|---|---|
| Sparse edges (E ≪ V²) | ✅ Better | ❌ Wasteful |
| Dense edges (E ≈ V²) | ❌ Overhead | ✅ Better |
| Edge lookup needed | ❌ O(degree) | ✅ O(1) |
| Memory constrained | ✅ O(V + E) | ❌ O(V²) |
Interview Tip
Always clarify: directed vs undirected? weighted vs unweighted? dense vs sparse? These answers determine your representation choice and algorithm selection.
Graph Traversal Overview
Why Graph Traversal Matters
Graph traversal is the foundation of most graph algorithms. Whether finding shortest paths, detecting cycles, or solving complex puzzles, traversal patterns are the building blocks.
Two Fundamental Approaches
Graph: 0 — 1
| |
2 — 3
BFS (Level by Level): DFS (Depth First):
Level 0: [0] Visit 0 → 1 → 3 → 2
Level 1: [1, 2] (goes deep, then backtracks)
Level 2: [3]
BFS vs DFS Comparison
| Aspect | BFS | DFS |
|---|---|---|
| Data Structure | Queue | Stack/Recursion |
| Pattern | Level-by-level | Go deep, backtrack |
| Shortest Path | ✅ (unweighted graphs) | ❌ |
| Space | O(V) worst case | O(V) worst case |
| Time | O(V + E) | O(V + E) |
| Use Case | Shortest path, level order | Cycle detection, topological sort |
Basic Template: Graph Traversal Setup
// Common graph building pattern for interview problems
public class GraphTraversal {
// Build graph from edge list
public static Map<Integer, List<Integer>> buildGraph(int n, int[][] edges) {
Map<Integer, List<Integer>> graph = new HashMap<>();
for (int i = 0; i < n; i++) {
graph.put(i, new ArrayList<>());
}
for (int[] edge : edges) {
graph.get(edge[0]).add(edge[1]);
graph.get(edge[1]).add(edge[0]); // Undirected
}
return graph;
}
// Build directed graph
public static Map<Integer, List<Integer>> buildDirectedGraph(int n, int[][] edges) {
Map<Integer, List<Integer>> graph = new HashMap<>();
for (int i = 0; i < n; i++) {
graph.put(i, new ArrayList<>());
}
for (int[] edge : edges) {
graph.get(edge[0]).add(edge[1]);
}
return graph;
}
// Build weighted graph
public static Map<Integer, List<int[]>> buildWeightedGraph(int n, int[][] edges) {
Map<Integer, List<int[]>> graph = new HashMap<>();
for (int i = 0; i < n; i++) {
graph.put(i, new ArrayList<>());
}
for (int[] edge : edges) {
graph.get(edge[0]).add(new int[]{edge[1], edge[2]});
graph.get(edge[1]).add(new int[]{edge[0], edge[2]});
}
return graph;
}
}
Visited Array/Set Pattern
// CRITICAL: Always track visited nodes to avoid infinite loops
boolean[] visited = new boolean[n];
// Or use Set for dynamic graphs
Set<Integer> visited = new HashSet<>();
// Common mistake: forgetting to mark as visited
// This causes infinite loops in cyclic graphs!
// Correct order:
visited[node] = true; // Mark BEFORE processing
process(node);
for (int neighbor : graph.get(node)) {
if (!visited[neighbor]) {
dfs(neighbor, visited);
}
}
Common Graph Problem Patterns
- Connected Components: Count/island problems
- Path Finding: Shortest path, valid paths
- Cycle Detection: Dependency resolution, deadlock detection
- Topological Sort: Task scheduling, course prerequisites
- Bipartite Checking: Two-coloring problems
Complexity Notes
- V = number of vertices, E = number of edges
- Both BFS and DFS: O(V + E) time, O(V) space
- Adjacency list traversal: O(V + E)
- Adjacency matrix traversal: O(V²)
Practice Problems
Given n nodes labeled from 0 to n-1 and a list of undirected edges, determine if the input graph forms a valid tree. A valid tree is connected and has no cycles.
Example:
Input: n = 5, edges = [[0,1],[0,2],[0,3],[1,4]]
Output: true
All 5 nodes are connected and there are exactly 4 edges (n-1), forming a valid tree.
Solution
```java
public boolean validTree(int n, int[][] edges) {
// A valid tree must have exactly n-1 edges
if (edges.length != n - 1) return false;
// Build adjacency list
Map<Integer, List<Integer>> graph = new HashMap<>();
for (int i = 0; i < n; i++) {
graph.put(i, new ArrayList<>());
}
for (int[] edge : edges) {
graph.get(edge[0]).add(edge[1]);
graph.get(edge[1]).add(edge[0]);
}
// BFS to check connectivity
boolean[] visited = new boolean[n];
Queue<Integer> queue = new LinkedList<>();
queue.offer(0);
visited[0] = true;
int count = 1;
while (!queue.isEmpty()) {
int node = queue.poll();
for (int neighbor : graph.get(node)) {
if (!visited[neighbor]) {
visited[neighbor] = true;
count++;
queue.offer(neighbor);
}
}
}
return count == n;
}
```Edge Cases:
- Single node with no edges is a valid tree
- Multiple disconnected components are not a valid tree
- Self-loops make it invalid
- Duplicate edges between same nodes
Quiz
1. What is the space complexity of an adjacency list representation for a graph with V vertices and E edges?
2. When should you prefer an adjacency matrix over an adjacency list?
3. What is the primary purpose of Graph Fundamentals?
4. What is a common mistake when implementing Graph Fundamentals?
Flashcards
Question
What is the difference between a directed and undirected graph?
Click to reveal answer
Answer
In a directed graph, edges have direction (u→v means you can go from u to v but not necessarily v to u). In an undirected graph, edges are bidirectional (u—v means you can traverse both ways).
Question
When is an adjacency list preferred over an adjacency matrix?
Click to reveal answer
Answer
Adjacency list is preferred for sparse graphs where E ≪ V². It uses O(V + E) space vs O(V²) for matrix. It's also better when iterating over neighbors is common.
Question
What is Graph Fundamentals?
Click to reveal answer
Answer
Graph Fundamentals is a key concept in software engineering.
Question
When to use Graph Fundamentals?
Click to reveal answer
Answer
Use Graph Fundamentals when building production systems that require reliability, scalability, and maintainability.
Question
Graph Fundamentals best practices
Click to reveal answer
Answer
Follow SOLID principles, write clean code, test thoroughly, document decisions, and monitor in production.
Revision Notes
Key Takeaways
- 1.A tree is a connected graph with exactly V-1 edges and no cycles
- 2.Adjacency list: O(V+E) space, preferred for sparse graphs
- 3.Adjacency matrix: O(V²) space, O(1) edge lookup
- 4.Always track visited nodes to prevent infinite loops in cyclic graphs
- 5.Clarify directed/undirected and weighted/unweighted before coding
Interview Tips
- •Start by asking: directed or undirected? weighted or unweighted?
- •For n nodes, a valid tree has exactly n-1 edges
- •Use BFS for shortest path in unweighted graphs
- •Use DFS for cycle detection and topological sort
- •Watch for disconnected graphs - may need to start from multiple nodes
Cheat Sheet
Graph Fundamentals Cheat Sheet
Graph Types
- Undirected: Edges go both ways
- Directed: Edges have direction
- Weighted: Edges have costs
- Unweighted: All edges equal
Representations
Adjacency List: Adjacency Matrix:
Space: O(V + E) Space: O(V²)
Edge lookup: O(degree) Edge lookup: O(1)
Add edge: O(1) Add edge: O(1)
Iterate neighbors: O(1) Iterate neighbors: O(V)
Best for: Sparse Best for: Dense
Java Template
// Adjacency List
Map<Integer, List<Integer>> graph = new HashMap<>();
for (int i = 0; i < n; i++) graph.put(i, new ArrayList<>());
for (int[] edge : edges) {
graph.get(edge[0]).add(edge[1]);
graph.get(edge[1]).add(edge[0]); // undirected
}
Key Formulas
- Tree: E = V - 1 (connected, no cycles)
- Complete graph: E = V(V-1)/2
- Degree sum: Σ degrees = 2E