Chapter 11

Graphs: Represent, Traverse, Analyse

A graph describes objects and the connections between them. Learn how to store those connections, explore them systematically, and use traversal to answer questions about reachability, dependencies and critical vertices.

Notation: V and E denote the vertex and edge sets; in complexity expressions, they mean the numbers of vertices and edges. An arrow u → v permits travel from u to v. In every trace, Next performs one operation, Back restores the previous state, and the slider jumps to a chosen step.

11.1Graph foundations

Definition A graph G = (V, E) consists of a set V of vertices (nodes) and a set E of edges (links) that connect pairs of vertices.

Where graphs appear

Web

Webpages are vertices; hyperlinks are edges.

Road network

Cities are vertices; roads are edges.

Social network

People are vertices; friendships are edges.

ER diagram

Entities are vertices; relationships are edges.

Directed, undirected, weighted, and unweighted

Directed — like a one-way road

A is the source and B is the destination. The edge permits A → B, but does not permit B → A.

Undirected — like a two-way rail link

A and B are adjacent to each other.

Weighted

Each edge carries a cost, distance, time, or another numerical weight.

Unweighted

Every edge is treated equally; path length is measured by the number of edges.

Sparse and dense graphs

Sparse graph

Has relatively few edges, commonly E = O(V).

Dense graph

Has close to the maximum possible number of edges, so E = Θ(V²).

Maximum edges in a simple graph An undirected simple graph has at most V(V − 1)/2 edges; a directed simple graph without self-loops has at most V(V − 1). Both are Θ(V²).

Degree of a vertex

  • In an undirected graph, deg(v) counts incident edge ends; a self-loop contributes two.
  • In a directed graph, in-degree counts incoming edges and out-degree counts outgoing edges.
  • Total directed degree is in-degree plus out-degree.

Self-loop, path, and cycle

Self-loop

An edge that connects a vertex to itself.

Path

A sequence of adjacent vertices with no repeated vertex, such as G → C → B → A → D → E.

Cycle

A closed path: the start is repeated at the end, but no other vertex is repeated.

Terminology A sequence that may repeat vertices is a walk. A path does not repeat vertices.

11.2Graph representations

A representation must preserve vertex relationships in memory. The two standard choices are an adjacency matrix and an adjacency list.

Adjacency matrix A |V| × |V| matrix A uses A[i][j] = 1 when an unweighted edge i → j exists, A[i][j] = w for a weighted edge, and A[i][j] = 0 otherwise. If zero is a valid edge weight, use a distinct marker such as None or ∞ for “no edge.”

Build both representations edge by edge

Graph: 0→1, 0→4, 1→2, 1→3, 1→4, 2→3, 3→4.

edge 0/7
Start with five vertices and no edges.
Python: adjacency matrix
class GraphMatrix:
    def __init__(self, vertices):
        self.vertices = vertices
        self.adj = [[0] * vertices for _ in range(vertices)]

    def add_edge(self, u, v):
        self.adj[u][v] = 1

    def display(self):
        for row in self.adj:
            print(row)

g = GraphMatrix(5)
for u, v in [(0,1), (0,4), (1,2), (1,3),
             (1,4), (2,3), (3,4)]:
    g.add_edge(u, v)
g.display()
Python: adjacency list
class GraphList:
    def __init__(self, vertices):
        self.vertices = vertices
        self.adj = {i: [] for i in range(vertices)}

    def add_edge(self, u, v):
        self.adj[u].append(v)

    def display(self):
        for vertex, neighbors in self.adj.items():
            print(vertex, ":", neighbors)
RepresentationSpaceEdge lookupBest for
Adjacency matrixO(V²)O(1)Dense graphs
Adjacency listO(V + E)O(deg(u)); O(V) worst caseSparse graphs
Adjacency-list size A directed graph stores one list entry per edge: E. An undirected graph stores each edge at both endpoints: 2E, which is asymptotically O(E).

11.3Traversal: visit each reachable vertex

Cycles

Unlike trees, graphs can represent cyclic road and computer networks.

Multiple paths

Graphs can offer several routes between two vertices, enabling shortest-path problems.

Bidirectional relations

Mutual friendships do not fit a strict parent–child hierarchy.

Many-to-many relations

Course prerequisites and other networks may have multiple parents and connections.

Graph traversal The systematic process of visiting graph vertices. The two principal methods are breadth-first search (BFS) and depth-first search (DFS).
  • Visited (discovered): the vertex has been seen.
  • Explored: all adjacent vertices of that vertex have been considered.

Applications include pathfinding, web crawling, social-network analysis, and network routing.

11.4Breadth-first search (BFS)

BFS explores the nearest vertices first. A first-in, first-out queue holds discovered vertices whose neighbours still need to be examined.

  1. Start: mark the source visited and put it in the queue.
  2. Process: remove the front vertex. Mark and enqueue each of its unvisited neighbours.
  3. Repeat: continue until the queue is empty.

BFS: discover vertices level by level

Directed graph; start at 0 and scan neighbours in the displayed adjacency-list order.

step 1
Watch the queue

After processing 0, the queue contains 1, 2 and 3. Vertex 4 is first discovered from 1. The later edge 2 → 4 must not enqueue it again.

Distance from 0 and first-discovery parent
VertexDistanceParent
Enqueue 0 and mark it visited.
BFS sequence: 0, 1, 2, 3, 4, 5, 6
View the Python implementation
from collections import deque

def bfs(graph, start):
    visited = {start}
    queue = deque([start])
    order = []

    while queue:
        u = queue.popleft()
        order.append(u)
        for v in graph[u]:
            if v not in visited:
                visited.add(v)
                queue.append(v)
    return order

Why BFS gives shortest paths

The queue processes distance d before distance d + 1. When v is first discovered from u, set distance[v] = distance[u] + 1 and parent[v] = u. Mark v immediately when enqueuing it, so two incoming edges cannot enqueue it twice. Following parent pointers backwards reconstructs a shortest path; for example, 0 → 1 → 4 → 6 has three edges.

The traversal order depends on neighbour order, but shortest-path distances do not. In the animation, an undiscovered vertex has distance ∞; the source has distance 0 and no parent.

Analysis

O(V)visited array
O(V)queue, worst case
O(V + E)adjacency list time
O(V²)adjacency matrix time

Initializing visited takes O(V). Across the traversal, adjacency-list scanning examines all vertices and edges once, giving O(V + E).

When to use BFS BFS can find mutual-friend candidates by exploring nearby social-network levels. In an unweighted graph, it also finds the minimum number of edges from the source to every reachable vertex.
Disconnected graphs One BFS or DFS call visits only the component containing its start vertex. To traverse the entire graph, scan all vertices and start a new traversal whenever an unvisited vertex is found.

11.5Depth-first search (DFS)

DFS follows one route as far as possible before trying another. Recursion remembers where to continue: returning from a call resumes the caller’s neighbour scan.

  1. Discover: mark the current vertex visited and record it.
  2. Explore: scan its outgoing neighbours in order. Recurse only on an unvisited neighbour.
  3. Return: once every neighbour is checked, pop the call and resume its parent.

DFS: follow calls and returns

Follow every call, neighbour check and return. Only the top frame executes.

step 1
Watch the return

Follow 0 → 1 → 4 → 6. When 6 has no outgoing neighbour, its frame is popped. Calls 4 and 1 then finish, allowing 0 to continue with neighbour 2.

1 Mark u visited; append u to order.
2 Take the next neighbour v of u, or go to 5.
3 If v is visited, continue at 2.
4 Call DFS(v), then continue at 2.
5 Return to the caller.
View neighbour order for this graph
Neighbour order used by this directed graph
VertexNeighbours, in order
Visit 0 and push it on the recursion stack.
DFS sequence: 0, 1, 4, 6, 2, 5, 3
View the Python implementation
def dfs(graph, node, visited=None, order=None):
    if visited is None:
        visited = set()
    if order is None:
        order = []
    visited.add(node)
    order.append(node)
    for neighbor in graph[node]:
        if neighbor not in visited:
            dfs(graph, neighbor, visited, order)
    return order

How to read the call stack

Local to each call
u is the vertex being explored; v is the neighbour currently being checked. The frame also remembers which neighbour comes next.
Shared by all calls
The visited set and discovery order belong to the whole traversal. Returning from a call does not erase them.
Suspended versus active
Only the top frame executes. A parent waits at step 4 while its child runs, then resumes at step 2.
Discovery versus completion
A vertex enters the traversal order when first visited. Its frame is removed only after every outgoing neighbour has been checked.

Example: by the time DFS reaches 2, vertex 4 has already finished. The edge 2 → 4 is checked and skipped; it does not create another call.

Analysis and comparison

DFS uses O(V) space for visited plus up to O(V) recursive calls or explicit stack entries. Its time is O(V + E) with adjacency lists and O(V²) with an adjacency matrix.

FeatureBFSDFS
ApproachLevel by levelDeep first, then backtrack
Data structureQueue (FIFO)Stack (LIFO) or recursion
Time with listsO(V + E)O(V + E)
Auxiliary spaceO(V)O(V)
Typical useUnweighted shortest pathMaze solving, cycle detection

11.6Topological sort and cycle detection

Topological ordering For every directed edge (u, v), u appears before v. A topological ordering exists only for a directed acyclic graph (DAG).

In a DFS implementation, insert each vertex at the front of the result when it finishes (equivalently, push it to a stack and reverse the finish stack).

Complexity and validity With adjacency lists, topological sorting takes O(V + E) time and O(V) auxiliary space. If DFS finds a back edge, the graph contains a directed cycle and no topological ordering exists.

DFS finish times → topological order

Watch vertices enter the finish stack, then leave in reverse order.

step 1
Watch when vertices are recorded

A vertex enters the finish stack after its descendants finish. The displayed order is obtained by popping that stack, not by copying DFS discovery order.

Start DFS at vertex 0.
One valid order: 0, 3, 2, 5, 1, 4, 6

Finish stack versus call stack: the call stack contains unfinished calls and shrinks on return. The finish stack records vertices only after all their outgoing neighbours are finished. Reversing that finish order places every prerequisite before the vertices that depend on it. For a disconnected DAG, start DFS from each still-unvisited vertex before reversing the complete finish order.

Clothing example and DFS times

Constraints include undershorts → pants, undershorts → shoes, pants → belt, pants → shoes, socks → shoes, shirt → belt, shirt → tie, belt → jacket, and tie → jacket. The watch is independent.

shirt 1/8tie 2/5 jacket 3/4belt 6/7 watch 9/10undershorts 11/16 pants 12/15shoes 13/14 socks 17/18

1/8 means discovery time 1 and finish time 8.

socks, undershorts, pants, shoes, watch, shirt, belt, tie, jacket

Cycle detection using DFS

For a directed graph, an edge to an already-visited vertex is not enough to prove a cycle. The important question is whether that vertex’s call is still active.

State of neighbour vMeaningAction for edge u → v
Unvisited (white)DFS has not reached v.Explore v recursively.
Active (grey)v is on the current call stack.A back edge closes a directed cycle.
Finished (black)v’s call has returned.This edge alone does not indicate a cycle.

Directed cycle trace

The red back edge closes 1 → 4 → 6 → 5 → 2 → 1.

ready
Trace the active DFS path.
Homework: detect a cycle in an undirected graph

Run DFS with a parent parameter. If an adjacent vertex is unvisited, recurse. If it is already visited and is not the current vertex’s parent, a cycle exists.

def has_cycle(graph):
    visited = set()
    def visit(u, parent):
        visited.add(u)
        for v in graph[u]:
            if v not in visited:
                if visit(v, u): return True
            elif v != parent:
                return True
        return False
    return any(visit(u, None) for u in graph if u not in visited)

11.7Articulation points (cut vertices)

Definition An articulation point is a vertex whose removal increases the number of connected components.

Let disc[u] be u’s discovery time and low[u] the smallest discovery time reachable from u’s DFS subtree using tree edges and at most one back edge.

DFS rule A non-root vertex u is an articulation point if it has a child v with low[v] ≥ disc[u]. The DFS root is an articulation point only if it has more than one DFS-tree child.
Vertex123456
Discovery time163245
Low value111133

Remove a vertex and count components

The slide graph has edges 1–4, 1–2, 4–3, 2–3, 3–5, 3–6, and 5–6.

connected
Choose a vertex to remove.
Result for the slide graph Vertex 3 is an articulation point. Removing it separates {5,6} from {1,2,4}.

How discovery and low values change

disc[u] records when u is first visited. low[u] records the earliest discovery time reachable from u’s subtree without using the edge back to its parent.

  1. On entry: set disc[u] = low[u] to the next discovery time.
  2. After a child v returns: set low[u] = min(low[u], low[v]).
  3. At a visited non-parent neighbour v: set low[u] = min(low[u], disc[v]).
  4. Test for separation: for a non-root u, low[v] ≥ disc[u] means that child’s subtree cannot reach an ancestor of u. For the root, count DFS-tree children: more than one makes it a cut vertex.

Second slide example

For the graph with edges G–C, C–B, C–D, B–A, A–D, D–F, F–E, and D–E, starting DFS at A gives discovery/low labels A 1/1, B 2/1, C 3/1, D 4/1, E 5/4, F 6/4, and G 7/7.

A graph with two cut vertices

The highlighted vertices separate the two leaf-side regions.

start at A
DFS labels: A 1/1 · B 2/1 · C 3/1 · D 4/1 · E 5/4 · F 6/4 · G 7/7
Answer The articulation points are C and D.
Maximum possible For n ≥ 2, a connected graph of order n can contain at most n − 2 cut vertices, attained by a path graph.

11.8Questions, applications, and shortest paths

Which DFS(a) sequences are valid?

Select a sequence. The exact graph from the slide will trace the proposed DFS path.

DFS sequence check

Start at a; adjacency-list order may vary.

choose an option
Choose one of the four proposed orders.

Valid sequences are 1, 3, and 4. Sequence 2 fails after a → b → f: vertex f still has unvisited neighbors h and g, so DFS cannot backtrack and visit e next.

What can be the maximum DFS stack length?

Follow the exact numbered path from the slide and watch the recursion stack grow.

Maximum-depth DFS path

The two unnumbered vertices are not on this active recursion path.

depth 1/19

On a small screen, scroll the graph horizontally to see all vertices.

Visit vertex 1; stack depth is 1.
Answer A DFS recursion stack can contain up to V vertices in the worst case. For this slide graph, the shown active path reaches 19 vertices.
Best representation for testing whether a vertex is isolated in an undirected graph

An adjacency list is convenient: the vertex is isolated exactly when its list is empty. With an adjacency matrix, its complete row must be checked. In a directed graph, isolation additionally requires both in-degree and out-degree to be zero.

Count connected components of an undirected graph using DFS

Scan all vertices. Whenever an unvisited vertex is found, start DFS and increment the component count. Complexity is O(V + E) with adjacency lists and O(V²) with an adjacency matrix. Directed graphs require a chosen notion of connectivity, such as weak or strong connectivity.

Detect strongly connected components using DFS

Kosaraju’s algorithm runs DFS to record finish order, transposes every directed edge, then runs DFS on the transpose in decreasing finish order. Each DFS tree in the second pass is one strongly connected component. Time: O(V + E).

Traversing a disconnected graph

Keep one visited set for the whole graph. Scan vertices in order; each unvisited vertex starts a new DFS tree and increases the component count. Returning from one DFS tree finishes only that component. An isolated vertex also forms a component.

One DFS tree per connected component

Scan A through F. The triangle, the pair and the isolated vertex form three components.

Watch the outer scan

A new component is counted only when the next vertex is still unvisited. F is isolated, but its single DFS call still counts as one component.

Shortest paths: weighted versus unweighted graphs

Unweighted graph

BFS explores level by level. The first arrival uses the fewest edges, so BFS gives shortest paths.

Weighted graph

BFS minimizes edge count, not total weight. Use Dijkstra’s algorithm when all edge weights are non-negative.

Slide counterexample In the weighted graph, S→M→D costs 7+2=9 while S→N→D costs 5+1=6. BFS may discover M first and cannot guarantee the minimum-weight route.
Why not DFS? DFS follows one path deeply and may take a long detour before reaching the target. It does not guarantee shortest paths in general graphs.
Review

Choose the algorithm by the question

QuestionMethodWhat to remember
Fewest edges from a source?BFSQueue + distance + parent; edges must have equal cost.
Explore paths and backtrack?DFSVisited set + call stack.
Order tasks with dependencies?Topological sortReverse DFS finish order; requires a DAG.
Does a directed graph contain a cycle?DFS with active-state trackingAn edge to an active ancestor proves a cycle.
Which vertices can disconnect an undirected graph?DFS with discovery and low valuesCheck each child subtree; handle the root separately.

These methods take O(V + E) time with adjacency lists. Before coding, identify whether the graph is directed, whether edge weights matter, and whether one traversal reaches every vertex.