Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction to Graphs

Six degrees of separation — you’re connected to any person on Earth through at most 6 people. Your friend knows someone who knows someone… and within six hops you can reach the President, a rice farmer in Vietnam, or a shepherd in Mongolia. Graphs prove it, and they explain exactly why it works.

Core Vocabulary

Before writing any code, get these terms locked in — they come up everywhere:

TermDefinitionExample
Vertex (node)A single entity in the graphA person, city, or webpage
EdgeA connection between two verticesA friendship, road, or hyperlink
PathA sequence of vertices connected by edgesLondon → Paris → Rome
CycleA path that starts and ends at the same vertexA → B → C → A
ConnectedEvery vertex can reach every other vertexA fully connected road network
WeightedEdges carry a numeric value (cost, distance)Road distances in km

Real-World Graphs

Graphs are not just a textbook concept — they model the most important systems in the world:

  • Social networks: people are vertices, friendships are edges. Facebook has ~3 billion vertices.
  • Road maps: cities are vertices, roads are edges, distances are weights. This is exactly what Google Maps uses.
  • Package managers: packages are vertices, dependencies are directed edges. When you run pip install numpy, Python resolves a dependency graph.
  • The Web: web pages are vertices, hyperlinks are directed edges. Google’s PageRank algorithm ranks pages by analysing this graph.

Representing a Graph in Code

You’ve drawn the graph on paper. Now how do you store it in memory? Two main approaches:

Adjacency Matrix

A 2D grid where matrix[i][j] = 1 means there’s an edge from vertex i to vertex j.

Consider this graph — 4 people and their friendships:

graph LR
    0["Alice (0)"]
    1["Bob (1)"]
    2["Carol (2)"]
    3["Dave (3)"]

    0 --- 1
    0 --- 2
    1 --- 3
    2 --- 3

The adjacency matrix for this graph looks like:

     Alice  Bob  Carol  Dave
Alice  [ 0,   1,    1,    0 ]
Bob    [ 1,   0,    0,    1 ]
Carol  [ 1,   0,    0,    1 ]
Dave   [ 0,   1,    1,    0 ]

A 1 at position [row][col] means those two people are friends.

Adjacency List

A dictionary where each vertex maps to a list of its neighbours.

graph LR
    A["Alice"] --> AL["[Bob, Carol]"]
    B["Bob"] --> BL["[Alice, Dave]"]
    C["Carol"] --> CL["[Alice, Dave]"]
    D["Dave"] --> DL["[Bob, Carol]"]

For sparse graphs (where most vertices connect to only a few others), the adjacency list uses far less memory than a matrix.

Implementing Both in Python

# The same graph represented two ways

# 4 people: 0=Alice, 1=Bob, 2=Carol, 3=Dave
# Friendships: Alice-Bob, Alice-Carol, Bob-Dave, Carol-Dave

# --- Adjacency Matrix ---
adj_matrix = [
    [0, 1, 1, 0],  # Alice connects to Bob(1) and Carol(2)
    [1, 0, 0, 1],  # Bob connects to Alice(0) and Dave(3)
    [1, 0, 0, 1],  # Carol connects to Alice(0) and Dave(3)
    [0, 1, 1, 0],  # Dave connects to Bob(1) and Carol(2)
]

names = ["Alice", "Bob", "Carol", "Dave"]

print("=== Adjacency Matrix ===")
print("     ", "  ".join(f"{n[:3]:>3}" for n in names))
for i, row in enumerate(adj_matrix):
    print(f"{names[i][:5]:>5}", row)

# Check if Alice(0) and Bob(1) are friends
print(f"\nAre Alice and Bob friends? {bool(adj_matrix[0][1])}")
# Check if Alice(0) and Dave(3) are friends
print(f"Are Alice and Dave friends? {bool(adj_matrix[0][3])}")

# --- Adjacency List ---
adj_list = {
    "Alice": ["Bob", "Carol"],
    "Bob":   ["Alice", "Dave"],
    "Carol": ["Alice", "Dave"],
    "Dave":  ["Bob", "Carol"],
}

print("\n=== Adjacency List ===")
for person, friends in adj_list.items():
    print(f"  {person} -> {friends}")

# Check if Alice and Bob are friends
print(f"\nAre Alice and Bob friends? {'Bob' in adj_list['Alice']}")
# Check if Alice and Dave are friends
print(f"Are Alice and Dave friends? {'Dave' in adj_list['Alice']}")

Matrix vs List — When to Use Which

Adjacency MatrixAdjacency List
SpaceO(V²) — always allocates V×VO(V + E) — only stores real edges
Check if edge existsO(1) — instant lookupO(degree) — scan neighbour list
List all neighboursO(V) — scan entire rowO(degree) — direct access
Best forDense graphs (many edges)Sparse graphs (few edges per node)

Most real-world graphs are sparse. Facebook users average ~338 friends out of 3 billion possible connections. The adjacency list wins almost every time.

Building a Weighted Graph

For a road map with distances, just store the weight alongside the neighbour:

# Weighted adjacency list — road distances in km
road_map = {
    "London": [("Paris", 344), ("Berlin", 932)],
    "Paris":  [("London", 344), ("Madrid", 1054), ("Rome", 1421)],
    "Berlin": [("London", 932), ("Rome", 1181)],
    "Madrid": [("Paris", 1054)],
    "Rome":   [("Paris", 1421), ("Berlin", 1181)],
}

print("Road map (weighted adjacency list):")
for city, connections in road_map.items():
    for neighbour, distance in connections:
        print(f"  {city} --[{distance} km]--> {neighbour}")

# Find the shortest direct road from London
london_connections = road_map["London"]
nearest = min(london_connections, key=lambda x: x[1])
print(f"\nNearest city to London by direct road: {nearest[0]} ({nearest[1]} km)")

Now you have the foundations. The next sections will show you how to actually traverse these graphs — visiting every vertex systematically to find paths, count components, and solve real problems.