Skip to content

Solutions: Graphs

Solutions to the practice problems at the end of Graphs.

I has degree 4: its neighbors are B, C, D, and H. The neighbors of B are D, E, F, G, and I. A, C, I is a path of length 2 from A to I. B, D, G, B is a cycle of length 3 (so is B, D, I, B). The graph is connected: every vertex can reach B, and B can reach every vertex.

Edge list: [("W", "X"), ("W", "Y"), ("Y", "X"), ("Z", "W"), ("Z", "Y")].

Adjacency matrix, with a 1 in a vertex’s row and another vertex’s column for an edge from the first to the second:

W X Y Z
W [ 0, 1, 1, 0 ]
X [ 0, 0, 0, 0 ]
Y [ 0, 1, 0, 0 ]
Z [ 1, 0, 1, 0 ]

Adjacency lists, as lists of indices in the order W, X, Y, Z: [[1, 2], [], [1], [0, 2]]. As lists of names: W has [X, Y], X has [], Y has [X], and Z has [W, Y].

Y has in-degree 2 (edges from W and Z) and out-degree 1 (the edge to X). The matrix is not symmetric: for example, W’s row has a 1 in X’s column, but X’s row has a 0 in W’s column, since the edge from W to X has no partner from X to W.

(a) Adjacency lists. The graph is very sparse: a billion vertices, but only a few hundred edges at each. An adjacency matrix would have 101810^{18} entries, nearly all 0; adjacency lists take space proportional to the number of friendships.

(b) An adjacency matrix. Every pair of cities has a distance, so the graph is as dense as it can be, and a 50 × 50 matrix of 2,500 distances takes no more space than adjacency lists would, while looking up any distance takes constant time.

(c) Adjacency lists, in a dictionary of lists. In an adjacency matrix stored as a list of lists, adding a vertex means extending every row and appending a new one, in Θ(n)\Theta(n) amortized time, and removing a vertex means deleting its row and an entry from the middle of every other row, in Θ(n2)\Theta(n^2) time. In a dictionary, adding a vertex is a single new key.

Each edge (x,y)(x, y) adds 1 to the degree of xx and 1 to the degree of yy, so each of the mm edges contributes exactly 2 to the sum of the degrees, which is therefore 2m2m. That sum is even. The vertices of even degree contribute an even amount to it, so the vertices of odd degree must contribute an even amount too. A sum of odd numbers is even only when there is an even number of them, so the number of vertices of odd degree is even.

For a directed graph, add_edge(u, v) appends v to u’s list, and not u to v’s:

def add_edge(self, u, v):
"""Add a directed edge from u to v."""
self.add_vertex(u)
self.add_vertex(v)
self.adjacent[u].append(v)
def in_degree(self, v):
count = 0
for u in self.adjacent:
if v in self.adjacent[u]:
count += 1
return count

in_degree searches every vertex’s list, taking Θ(n+m)\Theta(n + m) time in all: nn lists, holding mm items. To make it constant time, keep a second dictionary, mapping each vertex to the list of vertices with edges into it, and update it in add_edge as well. Then in_degree(v) is the length of v’s list in the second dictionary. This doubles the space for the edges, in exchange for fast access to each vertex’s incoming edges.