Skip to content
advancedPhase 5 · Graphs

Graph Fundamentals

Understand graph representations, properties, and basic operations.

1h 15m
5 problems
Topic Progress0%

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

  1. Connected Components: Count/island problems
  2. Path Finding: Shortest path, valid paths
  3. Cycle Detection: Dependency resolution, deadlock detection
  4. Topological Sort: Task scheduling, course prerequisites
  5. 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

0/1solved
Graph Valid Tree
Connected Components + Cycle Detection

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?

Question 1 options

2. When should you prefer an adjacency matrix over an adjacency list?

Question 2 options

3. What is the primary purpose of Graph Fundamentals?

Question 3 options

4. What is a common mistake when implementing Graph Fundamentals?

Question 4 options

Flashcards

Question

What is the difference between a directed and undirected graph?

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?

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?

Answer

Graph Fundamentals is a key concept in software engineering.

Question

When to use Graph Fundamentals?

Answer

Use Graph Fundamentals when building production systems that require reliability, scalability, and maintainability.

Question

Graph Fundamentals best practices

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