Adjacency list
An adjacency list keeps, for every vertex of a graph, the list of its neighbours. Most graph algorithms take a vertex and go over its neighbours, and the adjacency list gives them in time proportional to their number.
It is the usual way to store a graph read from the input. Take an adjacency matrix instead when the graph has at most about 5000 vertices and the program mostly asks whether there is an edge between two given vertices.
How it works
A graph has 8 vertices, and the input gives its 9 edges one per line, as pairs of vertices: 1 2, 1 3, 2 4, 3 4, 2 5, 4 6, 3 6, 6 7, 4 5.
Fig. 1. Row holds the list of vertex , one neighbour per cell; row 0 is not used. The list of 4 follows the order of the input lines that mention 4: 2 4, 3 4, 4 6, 4 5. Vertex 8 has no neighbours.
The number of neighbours of a vertex is its degree, written : the length of its list. Vertex 4 has degree 4, and vertex 8 has degree 0.
Before any edge is read, every vertex gets an empty list, including vertex 8, which no line mentions. Then each line makes its two ends neighbours of each other: 4 6 writes 6 at the end of the list of 4, and 4 at the end of the list of 6.
Every vertex starts with an empty list, vertex 8 too.
Fig. 2. Each line read adds one number to each of two lists (blue).
After the nine lines the lists are the ones in Fig. 1. Each line adds two numbers, so the lists hold 18 numbers in all, two for every edge.
To check whether there is an edge between and , look for in the list of : there is no 7 in the list of 4, so 4 and 7 are not joined.
With vertices and edges, the costs in big-O notation are:
Operation | Cost |
|---|---|
Create empty lists | |
Add an edge | on average |
Go over the neighbours of | O(\deg v) |
Degree of | |
Is there an edge between and ? | O(\deg u) |
Memory | O(n + m) |
In C++
The vertices are numbered from 1 to n, and the lists are a vector of vectors:
vector<vector<int>> adj(n + 1);This creates n + 1 empty lists, so adj[v] is the list of vertex v, and adj[0] stays unused. adj[u].push_back(v) adds v to the list of u, for (int u : adj[v]) goes over the neighbours of v, and adj[v].size() is the degree of v.
The program reads n and m, then m lines u v, builds the lists of an undirected graph and prints them; the marked line is the one to remove for a directed graph. For the example the input is:
8 9
1 2
1 3
2 4
3 4
2 5
4 6
3 6
6 7
4 5#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
// create the lists
vector<vector<int>> adj(n + 1);
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
// add the edge to both lists
adj[u].push_back(v);
adj[v].push_back(u); // remove this line if the graph is directed
}
for (int v = 1; v <= n; v++) {
cout << v << ":";
// go over the neighbours of v
for (int u : adj[v]) {
cout << ' ' << u;
}
cout << '\n';
}
}Building the lists and going over all of them both take O(n + m) time.
Directed graphs
In a directed graph, the line u v is an edge from u to v, so it goes only into the list of u: remove the marked line adj[v].push_back(u); from the program.
Fig. 3. An edge goes only into the list of the vertex it leaves.
Weighted edges
When each edge has a weight, a line of the input is u v w, and each entry of a list holds a neighbour together with the weight of the edge to it, as a pair. Three places of the program change:
// create the lists
vector<vector<pair<int, int>>> adj(n + 1);
int u, v, w;
cin >> u >> v >> w;
// add the edge to both lists
adj[u].push_back({v, w});
adj[v].push_back({u, w});
// go over the neighbours of v
for (auto [u, w] : adj[v]) {
cout << " (" << u << ", " << w << ")";
}The loop for (auto [u, w] : adj[v]) (C++17) takes each pair apart: u is its .first, the neighbour, and w its .second, the weight.
Fig. 4. Each entry holds a neighbour and the weight of the edge to it. The edge between 4 and 6 has weight 2, so the list of 4 holds (6, 2).
A weight usually fits in int, but a sum of many weights, such as the length of a long path, often needs long long.
Examples of use
Breadth-first search and depth-first search take each vertex once and read its list once, so both run in O(n + m).
Dijkstra's algorithm reads the weighted lists in the same way, using the weight of each entry.
A tree on vertices comes as n - 1 edges and is stored in the same way. A program going down from a root meets the parent of each vertex in its list too, and skips it with if (u == parent) continue;.
Common mistakes
Adding an undirected edge to one list only, so some vertices keep empty lists though they have neighbours. Write both
push_backlines.Creating
adj(n)while the vertices are numbered from 1, so an edge to vertexnwrites past the end of the vector. Createadj(n + 1).Not clearing the lists between test cases:
adj.resize(n + 1)keeps the old lists. Declareadjinside the loop over tests, or calladj.assign(n + 1, {}).Taking the size of a list as the number of different neighbours when edges can repeat. To remove repeats, sort the list, then run
adj[v].erase(unique(adj[v].begin(), adj[v].end()), adj[v].end());.
Similar structures
Adjacency list | Adjacency matrix | Edge list | |
|---|---|---|---|
Memory | O(n + m) | O(n^2) | |
Neighbours of | O(\deg v) | ||
Is there an edge between and ? | O(\deg u) | ||
Add an edge | on average |
Adjacency matrix
An adjacency matrix is an n \times n table: the cell in row and column is 1 if there is an edge between and , and 0 otherwise.
Fig. 5. Row 4 has a 1 in columns 2, 3, 5 and 6, the neighbours of vertex 4. The cell in row 4, column 7 is 0.
Use it when the input already is a matrix, or for Floyd–Warshall. It needs n^2 cells, so it fits up to about 5000 vertices.
Edge list
An edge list keeps the edges as they were read, in a vector of pairs or triples. Use it when an algorithm goes over all edges in rounds, as Bellman–Ford does, or sorts them by weight, as Kruskal's algorithm does.
Practice
Very easy · Graphs
Easy · Graphs
Very easy · Graphs
Very easy · Graphs