Adjacency matrix and adjacency list

Two ways to store a graph, and choosing between them for dense and sparse graphs.

A4.2Trees and graphsA level25 min

Do this lesson in the simulator

A list of edges stores a graph, but every question you ask it means searching all the edges. Programs store graphs in one of two standard ways instead: an adjacency matrix or an adjacency list. Knowing both, and being able to justify choosing one, is a favourite exam question.

The adjacency matrix

An adjacency matrix is a two-dimensional array with one row and one column for each vertex. The cell in row u, column v records the edge from u to v. At GCSE you used two-dimensional arrays for grids of readings; here the grid records who is joined to whom.

In an unweighted graph each cell holds 1 for an edge and 0 for none. In a weighted graph it holds the weight. This is the warehouse graph from the last lesson:

A weighted undirected graph of six zones53462781ABCDEF
A weighted, undirected graph: six vertices, eight edges, each edge labelled with its weight
A B C D E F
A 0 5 0 3 0 0
B 5 0 4 6 2 0
C 0 4 0 0 0 7
D 3 6 0 0 8 0
E 0 2 0 8 0 1
F 0 0 7 0 1 0

Three things to notice:

  • The graph is undirected, so the matrix is symmetric about the leading diagonal (top left to bottom right): row B, column E holds the same 2 as row E, column B. A directed graph's matrix is usually not symmetric.
  • The leading diagonal is all 0, because no vertex has an edge to itself.
  • Here 0 means "no edge". If 0 could be a real weight, use a value that cannot be one, such as infinity (∞) or None. Exam questions often write ∞ or leave the cell blank.
# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()

VERTICES = ["A", "B", "C", "D", "E", "F"]
EDGES = [("A", "B", 5), ("A", "D", 3), ("B", "C", 4), ("B", "D", 6),
         ("B", "E", 2), ("C", "F", 7), ("D", "E", 8), ("E", "F", 1)]

n = len(VERTICES)
matrix = [[0] * n for i in range(n)]     # n rows, each its own list of n zeros
for u, v, w in EDGES:
    i = VERTICES.index(u)
    j = VERTICES.index(v)
    matrix[i][j] = w
    matrix[j][i] = w                     # undirected: the edge goes both ways

for i in range(n):
    print(VERTICES[i], matrix[i])

print("B to E:", matrix[1][4])           # one look answers "is there an edge?"
print("C to D:", matrix[2][3])

Run this in the simulator

[[0] * n for i in range(n)] makes n separate rows. The shortcut [[0] * n] * n does not: it puts the same row in the list n times, so setting one cell would change that column in every row.

The adjacency list

An adjacency list stores, for each vertex, a list of only the vertices it is joined to. In Python a dictionary of lists does this naturally; in a language without dictionaries it is an array of linked lists. For a weighted graph each entry holds the neighbour and the weight.

Vertex Adjacent vertices (weight)
A B (5), D (3)
B A (5), C (4), D (6), E (2)
C B (4), F (7)
D A (3), B (6), E (8)
E B (2), D (8), F (1)
F C (7), E (1)
# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()

GRAPH = {
    "A": [("B", 5), ("D", 3)],
    "B": [("A", 5), ("C", 4), ("D", 6), ("E", 2)],
    "C": [("B", 4), ("F", 7)],
    "D": [("A", 3), ("B", 6), ("E", 8)],
    "E": [("B", 2), ("D", 8), ("F", 1)],
    "F": [("C", 7), ("E", 1)],
}

for neighbour, w in GRAPH["B"]:
    print("B to", neighbour, "takes", w, "s")

# is there an edge from B to E? search B's list
found = False
for neighbour, w in GRAPH["B"]:
    if neighbour == "E":
        found = True
print("B to E:", found)

entries = 0
for v in GRAPH:
    entries = entries + len(GRAPH[v])
print("entries:", entries)

Run this in the simulator

The list has 16 entries for 8 edges, because an undirected edge appears in the lists of both of its ends: the handshake lemma again. A directed edge appears once, in the list of the vertex it leaves.

Which to use

Write V for the number of vertices and E for the number of edges.

Adjacency matrix Adjacency list
Memory a cell for every pair of vertices: V² cells an entry for each edge (two for an undirected edge), plus a list per vertex
Is u joined to v? one look at matrix[u][v], however big the graph search u's list, as long as u's degree
Every neighbour of u scan the whole of row u: V cells exactly u's neighbours, no more
Add or remove an edge change one cell (two if undirected) add to or remove from a list
Add a vertex a new row and a new column a new, empty list
Suits dense graphs, with many of the possible edges present, and programs that test for edges often sparse graphs, with few edges per vertex, and programs that go from a vertex to its neighbours

A worked example. Divide a large mat into a 10 by 10 grid of zones, with an edge between zones side by side. That is 100 vertices, so a matrix has 100 × 100 = 10,000 cells. But each zone has at most 4 neighbours: the grid has 90 edges across and 90 up and down, 180 in total, so the adjacency list holds only 360 entries. Nearly every cell of the matrix would be 0. The graph is sparse, and the list is the right choice.

Now take direct flights between 20 airports where almost every pair is connected. The matrix has 400 cells and nearly all of them are used, so it wastes little memory, and it answers "is there a direct flight from X to Y?" in one look. That graph is dense, and the matrix is a sound choice.

The traversals in the next two lessons spend their time going from each vertex to its neighbours, which is exactly what the list is good at.

Task: matrix and list

The graph in EDGES is directed and weighted, as drawn below: ("A", "B", 4) is an edge from A to B of weight 4, and there is no edge from B to A unless one is listed. VERTICES gives the order of the matrix's rows and columns. Build an adjacency matrix called matrix, a list of lists with matrix[i][j] holding the weight of the edge from VERTICES[i] to VERTICES[j] and 0 where there is no edge, and print it one row per line: the vertex, a colon, then the row's values separated by single spaces, such as A: 0 4 0 7. Then build an adjacency list as a dictionary, with each vertex's neighbours in the order their edges appear in EDGES, and print it one vertex per line in VERTICES order, in the form A -> B(4) D(7). Build both from EDGES: do not type the rows.

The directed weighted graph for the task473251ABCD
The task's graph: directed and weighted
# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()

VERTICES = ["A", "B", "C", "D"]
EDGES = [("A", "B", 4), ("A", "D", 7), ("B", "C", 3), ("C", "A", 2), ("D", "C", 5), ("B", "D", 1)]

n = len(VERTICES)

Challenges

  1. Print the matrix with every arrow reversed. What does row A tell you now?
  2. Write in_degree(v) using the matrix and out_degree(v) using the list. Which representation makes each one easier, and why?
  3. A graph has 1,000 vertices and each has about 5 neighbours. How many cells does its adjacency matrix have? About how many entries does its adjacency list hold if the edges are directed?