Graphs

A Graph is a collection of Vertices/Nodes (denoted as ) and Edges (denoted as ) that connect pairs of vertices.

If the Edges have a direction, then we have a Directed Graph:

exampleaabba->bcca->cddb->dc->d

Otherwise, we have an Undirected Graph:

exampleaabba--bcca--cddb--dc--d

For both graphs, the nodes are a, b, c and d. However, the edges differ between the two.

In fact, if we group the edges for the single nodes, we would have the following ones for the Directed graph:

a -> {b, c}
b -> {d}
c -> {d}
d -> {}

And these for the Undirected one:

a -> {b, c}
b -> {a, d}
c -> {a, d}
d -> {b, c}

So, we simply mapped the nodes with a list of so called Neighbors.

Connected and Disconnected Graphs

The previous Graphs were all Connected, since there was a path between each Node. However, we can also have Disconnected ones:

exampleccddc--daabba--b

In this case, there is no connection between the nodes a, b and c, d.

Representation

The most common way to represent a Graph is within an Adjacency List, which can be a Python dictionary that maps the Node to the set of Neighbors.

Usually, a list of Edges is provided, where each element is either a list or a tuple of two elements. For example:

edges = [
    ["a", "b"],
    ["a", "c"],
]

Adjacency List for a Directed Graph

Since the Graph is Directed, we just need to consider a single dependency between two nodes (a single add call).

exampleaabba->bcca->c

from collections import defaultdict

edges = [
    ("a", "b"),
    ("a", "c"),
]

graph = defaultdict(list)
for a, b in edges:
    graph[a].append(b)

Adjacency List for an Undirected Graph

For the Undirected one, we need a double dependency between the two nodes. In fact, node a has b as Neighbor, and vice versa.

exampleaabba--bcca--c

from collections import defaultdict

edges = [
    ("a", "b"),
    ("a", "c"),
]

graph = defaultdict(list)
for a, b in edges:
    graph[a].append(b)
    graph[b].append(a)  # the only difference compared to the other implementation