Six routers and nine cables: two triangles, and , joined by the three cables –, – and –. This graph is called the prism.
Whichever single router fails, the other five can still reach each other. You can check that by deleting each router in turn. But that is six separate experiments, and it does not tell you why the network is robust, or how to design another one that is. This module gives two better answers. The first is local: between any two routers there are two routes that share no router in between. The second is a recipe: the network can be built from a cycle by repeatedly adding a path between two routers already present. Both turn out to be equivalent to "no single failure disconnects".
from itertools import combinations
from collections import deque
def network(edges):
g = {}
for u, v in edges:
g.setdefault(u, set()).add(v)
g.setdefault(v, set()).add(u)
return g
def connected(g, removed=()):
gone = set(removed)
left = [v for v in g if v not in gone]
if len(left) <= 1:
return True
seen, queue = {left[0]}, deque([left[0]])
while queue:
u = queue.popleft()
for w in g[u]:
if w not in gone and w not in seen:
seen.add(w)
queue.append(w)
return len(seen) == len(left)
def two_connected(g):
"""By brute force: at least 3 vertices, connected, and no single deletion disconnects."""
return len(g) >= 3 and connected(g) and all(connected(g, [v]) for v in g)
prism = network([(0, 1), (1, 2), (2, 0), (3, 4), (4, 5), (5, 3), (0, 3), (1, 4), (2, 5)])
assert two_connected(prism)
The brute-force test deletes each of the vertices and searches what is left: searches, each over the whole graph. It answers "yes" or "no" but leaves no evidence behind, and it says nothing about how to build such a network. Each deletion re-explores almost the same graph.
A graph is 2-connected if it has at least 3 vertices, is connected, and has no cut vertex. (This is from module 14. The complete graph has no cut vertex but only two vertices, and , so it is not 2-connected.)
Two -paths are internally disjoint if they share no vertex other than and . In the prism, and are internally disjoint -paths.
Whitney's theorem (1932)
Let have at least 3 vertices. is 2-connected if and only if every two vertices of are joined by two internally disjoint -paths.
Proof. If. Suppose every pair has two internally disjoint paths. Deleting one vertex cannot separate two other vertices and , because lies on at most one of their two paths. So is connected for every , and is connected, with at least 3 vertices.
Only if. Let be 2-connected. We prove by induction on the distance that every two vertices have two internally disjoint -paths.
Base, . The edge is one path. is still connected: otherwise is a cut edge, and since has at least 3 vertices, one of has another neighbor and is a cut vertex. So has a -path, which is the second.
Step, . Let be the vertex before on a shortest -path, so . By induction there are internally disjoint -paths and . Since is 2-connected, has a -path .
Proof moves used here.
Expansion lemma. If is 2-connected and is obtained by adding a new vertex adjacent to at least two vertices of , then is 2-connected.
Proof. has at least 4 vertices and is connected. Delete one vertex. Deleting leaves , which is connected. Deleting some of leaves , connected because is 2-connected, together with , which still has a neighbor in because it had at least two. So no vertex of is a cut vertex.
Two internally disjoint -paths together form a cycle through and , and a cycle through and splits at them into two such paths. So Whitney's theorem can be restated:
Corollary. A graph with at least 3 vertices is 2-connected if and only if every two of its vertices lie on a common cycle.
In the prism, and lie on the cycle :
def internally_disjoint(p, q):
return p[0] == q[0] and p[-1] == q[-1] and not set(p[1:-1]) & set(q[1:-1])
def is_path(g, p):
return len(set(p)) == len(p) and all(b in g[a] for a, b in zip(p, p[1:]))
p, q = [0, 3, 4], [0, 1, 4]
assert is_path(prism, p) and is_path(prism, q) and internally_disjoint(p, q)
No, and that is why the theorem says "at least 3 vertices". has no cut vertex, yet its two vertices are joined by only one path. With the definition used here, is not 2-connected, so the theorem is not contradicted; the hypothesis keeps it out.
An ear of a subgraph is a path in whose two endpoints are distinct vertices of , whose other vertices (if any) are not in , and whose edges are not in . A single edge of outside between two vertices of counts. An ear decomposition of is a sequence: a cycle, then ears added one at a time, until every edge of is used.
Here is one for the prism. Start with the triangle ; add the ear ; then the ear ; then the single-edge ear –:
def valid_ears(g, cycle, ears):
"""Check an ear decomposition (a stand-in checker, not a way to find one)."""
if len(cycle) < 3 or not is_path(g, cycle) or cycle[0] not in g[cycle[-1]]:
return False
built = set(cycle)
used = {frozenset(e) for e in zip(cycle, cycle[1:] + cycle[:1])}
for ear in ears:
ends, inside = (ear[0], ear[-1]), ear[1:-1]
new = {frozenset(e) for e in zip(ear, ear[1:])}
if ends[0] == ends[1] or not set(ends) <= built or set(inside) & built or not is_path(g, ear):
return False
if new & used:
return False
used |= new
built |= set(inside)
return built == set(g) and used == {frozenset((u, w)) for u in g for w in g[u]}
assert valid_ears(prism, [0, 1, 2], [[0, 3, 4, 1], [2, 5, 4], [3, 5]])
assert not valid_ears(prism, [0, 1, 2], [[0, 1], [0, 3, 4, 1], [2, 5, 4], [3, 5]]) # 0-1 is reused
Whitney's ear theorem
A graph is 2-connected if and only if it has an ear decomposition. Moreover, every cycle of a 2-connected graph can be the starting cycle.
Proof. If. A cycle is 2-connected. Adding an ear to a 2-connected keeps it 2-connected: the new graph has at least 3 vertices and is connected. Delete a vertex . If is in , then is connected, and the ear, minus if is an endpoint, still hangs from the other endpoint, which is in because the endpoints are distinct. If is inside the ear, is untouched and each remaining piece of the ear is attached to an endpoint. Either way the rest is connected.
Only if. Let be 2-connected and any cycle of it (one exists: by the corollary, any two vertices lie on a cycle). Among the subgraphs that can be built from by adding ears, take a maximal one, . Suppose .
So , and has an ear decomposition starting from .
Proof moves used here.
The word distinct in "two distinct endpoints" matters. Take two copies of that share one vertex. You can start from a triangle in one copy and add ears until that copy is used up. To reach the other copy, any path from the first must leave and return through the shared vertex, so it is a closed ear: both ends are the same vertex. That graph has a cut vertex, so it is not 2-connected, and indeed it has no ear decomposition with open ears.
left = list(combinations(range(4), 2))
shared = network(left + [(a + 3, b + 3) for a, b in left]) # both copies contain vertex 3
assert not two_connected(shared) and not connected(shared, [3])
An ear decomposition is a certificate of 2-connectedness: checking one, as valid_ears does,
takes one pass over the edges. A cut vertex is a certificate of the opposite: delete it and
search once. The brute-force test instead runs searches. Hopcroft and Tarjan (1973) found
all cut vertices of a graph in one depth-first search, in time.
A depot ships to three warehouses through a network of hubs. A hub can fail, and the company wants routes from the depot to the three warehouses that share no hub at all, so that one failure costs at most one delivery. When can it be done, and what short evidence shows it cannot? Nothing in the question mentions cycles or ears. It is a question about routes that avoid each other, and module 16 answers it.
The lab runs in your browser. Your code grows an ear decomposition one ear per frame and refuses to be fooled by an ear that closes on itself. It then finds two disjoint routes where the obvious first choice blocks the second. Half of the lab is proofs. You assemble a proof that a small change to a 2-connected graph keeps it 2-connected, find the broken step in a near-copy of Whitney's proof and build the graph that breaks it, and finish with a problem that names no technique, where you write the proof yourself.
is_biconnected and articulation_points find cut vertices
with a depth-first search (the Hopcroft–Tarjan approach).After the lab, the tutor will ask you to defend your work out loud:
Topic list modeled on the public syllabus of UIUC Math 412 (Graph Theory), which follows D. B. West, Introduction to Graph Theory, 2nd ed., Chapters 1–7. All lessons, proofs, examples, code and exercises here are original. This course is independent and not affiliated with or endorsed by the University of Illinois.