Skip to content
Laviyx Learn

Try

DSA course menu

Graphs in Data Structures: Terminology and Representation in Python

Learn graph basics: vertices and edges, directed and undirected, weighted graphs, degree, paths and cycles, and how to store a graph as an adjacency list, adjacency matrix or edge list in Python.

Intermediate9 min readUpdated
On this page

A graph is a set of vertices (also called nodes) connected by edges. Graphs model anything made of connections: roads between cities, friendships, links between web pages, dependencies between tasks, or moves in a game. A tree is a special kind of graph with no cycles.

Text
   A ─── B
   │   / │
   │  /  │
   C ─── D ─── E

Vocabulary#

TermMeaning
vertex (node)one item: a city, a person, a page
edgea connection between two vertices
undirectededges work both ways (friendship)
directededges have a direction, A → B (following someone, a one-way street)
weightedeach edge has a number: distance, cost, time
neighboursthe vertices directly connected to a vertex
degreenumber of edges at a vertex (in-degree / out-degree for directed graphs)
patha sequence of vertices joined by edges
cyclea path that returns to where it started
connectedevery vertex can reach every other (undirected)
DAGa directed acyclic graph: directed, with no cycles

We write V for the number of vertices and E for the number of edges. Graph algorithm costs are given in terms of both, like O(V + E).

Adjacency list#

The most common representation: for each vertex, a list of its neighbours. In Python, a dictionary of lists:

Python
from collections import defaultdict

edges = [("A", "B"), ("A", "C"), ("B", "C"), ("B", "D"), ("C", "D"), ("D", "E")]

graph = defaultdict(list)
for u, v in edges:
    graph[u].append(v)
    graph[v].append(u)      # undirected: add both directions

for vertex in sorted(graph):
    print(vertex, "->", graph[vertex])
print("degree of D:", len(graph["D"]))
Output
A -> ['B', 'C']
B -> ['A', 'C', 'D']
C -> ['A', 'B', 'D']
D -> ['B', 'C', 'E']
E -> ['D']
degree of D: 3

Space is O(V + E), and listing a vertex's neighbours costs O(its degree). This is the right choice for most real graphs, which are sparse (each vertex connects to only a few others).

Adjacency matrix#

A V × V grid where matrix[i][j] is 1 (or the weight) if there is an edge from vertex i to vertex j:

Python
names = ["A", "B", "C", "D", "E"]
index = {name: i for i, name in enumerate(names)}
edges = [("A", "B"), ("A", "C"), ("B", "C"), ("B", "D"), ("C", "D"), ("D", "E")]

matrix = [[0] * len(names) for _ in names]
for u, v in edges:
    matrix[index[u]][index[v]] = 1
    matrix[index[v]][index[u]] = 1

print("   " + " ".join(names))
for name, row in zip(names, matrix):
    print(name, row)
print("B-D connected?", matrix[index["B"]][index["D"]] == 1)
Output
   A B C D E
A [0, 1, 1, 0, 0]
B [1, 0, 1, 1, 0]
C [1, 1, 0, 1, 0]
D [0, 1, 1, 0, 1]
E [0, 0, 0, 1, 0]
B-D connected? True

Checking whether two specific vertices are connected is O(1), but the matrix always uses O(V²) space, even when there are few edges. It suits small or dense graphs.

Directed and weighted graphs#

For a weighted directed graph, store (neighbour, weight) pairs, and add each edge in one direction only:

Python
roads = {
    "Home":   [("Market", 4), ("School", 2)],
    "School": [("Market", 1), ("Park", 7)],
    "Market": [("Park", 3)],
    "Park":   [],
}
for place, out in roads.items():
    for to, km in out:
        print(f"{place} -> {to}: {km} km")

in_degree = {p: 0 for p in roads}
for out in roads.values():
    for to, _ in out:
        in_degree[to] += 1
print(in_degree)
Output
Home -> Market: 4 km
Home -> School: 2 km
School -> Market: 1 km
School -> Park: 7 km
Market -> Park: 3 km
{'Home': 0, 'School': 1, 'Market': 2, 'Park': 2}

Finding the cheapest route here is the job of Dijkstra's algorithm.

Edge list#

Sometimes a plain list of (u, v, weight) triples is enough. Algorithms that process edges in order of weight, like Kruskal's minimum spanning tree, use it directly.

Grids are graphs too#

A 2D grid is a graph where each cell is a vertex connected to its up, down, left and right neighbours. You usually do not build an adjacency list; you compute neighbours on the fly:

Python
grid = [
    "..#",
    ".#.",
    "...",
]

def neighbours(r, c):
    for dr, dc in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
        nr, nc = r + dr, c + dc
        if 0 <= nr < len(grid) and 0 <= nc < len(grid[0]) and grid[nr][nc] != "#":
            yield nr, nc

print(list(neighbours(0, 0)))
print(list(neighbours(2, 1)))
Output
[(1, 0), (0, 1)]
[(2, 0), (2, 2)]

# cells are walls. Maze and map problems, and game pathfinding, work on graphs like this.

Which representation?#

Adjacency listAdjacency matrixEdge list
spaceO(V + E)O(V²)O(E)
are u and v connected?O(degree)O(1)O(E)
visit all neighbours of uO(degree)O(V)O(E)
best formost graphs (sparse)small or dense graphssorting edges by weight

Common mistakes#

Practice#

Practice exercise

Given a directed graph as an adjacency list, write reverse_graph(graph) that returns a new graph with every edge pointing the other way, keeping vertices that have no edges.

Show hint

Start the new graph with an empty list for every vertex, then for each edge u → v in the old graph, add u to the new list of v.

Show solution
Python
def reverse_graph(graph):
    reversed_g = {v: [] for v in graph}
    for u, neighbours in graph.items():
        for v in neighbours:
            reversed_g.setdefault(v, []).append(u)
    return reversed_g

follows = {"Asha": ["Ravi", "Meera"], "Ravi": ["Meera"], "Meera": [], "John": []}
print(reverse_graph(follows))
Output
{'Asha': [], 'Ravi': ['Asha'], 'Meera': ['Asha', 'Ravi'], 'John': []}

In the reversed graph, each person's list shows their followers. Reversing a graph is a step in some advanced algorithms, such as finding strongly connected components.

Key takeaway#

Next: exploring a graph level by level, with Breadth-First Search (BFS).

Finished this lesson?