Monocurl Essays
Series Download

Bridges of Königsberg: Euler paths and circuits

Seven bridges and a walk that nobody could find. Counting the edges at each vertex proves it cannot exist and settles every draw-without-lifting puzzle.

23 min·High school and early college·

A map of Königsberg in the 1730s: a river, an island, and seven bridges. A walker tries to cross every bridge exactly once, and after six bridges is stuck with one bridge still uncrossed.
Monocurl source
let ORDER = [[0, 0], [2, 1], [5, 0], [6, 1], [1, 1], [4, 0]]
var route_build = []
var marks_build = []
for (k in range(0, len(ORDER))) {
    let e = ORDER[k][0]
    let raw = [MC[KE[e][0]], BR[e][0], BR[e][1], MC[KE[e][1]]]
    let leg = [raw, reverse(raw)][ORDER[k][1]]
    for (s in range(0, 3)) {
        let n = to_int(ceil(dist(leg[s], leg[s + 1]) / 0.08))
        for (j in range([1, 0][k == 0 and s == 0], n + 1)) {
            route_build .= lerp(leg[s], leg[s + 1], j / n)
        }
        if (s == 1) {
            marks_build .= len(route_build) - 1
        }
    }
}
let ROUTE = route_build
let MARKS = marks_build
let Stroll = |u| {
    let x = u * (len(ROUTE) - 1)
    return [
        TrailLine(ROUTE, u),
        Mover(ROUTE, u, EMBER, 0.14),
        center{[-0.45, 1.88, 0]} scale{1.25} color{EMBER} Number(count(MARKS, |m| x >= m - 0.001), 0)
    ]
}
mesh water = Water(0.22)
mesh land = Lands(MC, 1)
mesh bridges = map(BR, |b| Seg(b[0], b[1], BRIDGE_COL, BRIDGE_W))
mesh words = center{[-2.15, 1.88, 0]} Cap("bridges crossed:", 0.8, INK)
mesh stroll = Stroll(u: 0)
mesh verdict = []

slide "walk"
    play Wait(0.8)
    stroll.u = 1
    play Lerp(7, [&stroll], linear)
    verdict = [
        Seg(BR[3][0], BR[3][1], ROSE, BRIDGE_W),
        center{[-1.0, -1.9, 0]} Cap("never crossed", 0.8, ROSE),
        center{[3.05, 0, 0]} Cap("stuck", 0.8, ROSE)
    ]
    play Fade(0.6, [&verdict])
    play Wait(2)

Uses the shared definitions for this lesson's figures.

The city of Königsberg stood on a river with an island in the middle, and in the 1730s seven bridges joined the island, the two banks and the land to the east, as on the map above. The townspeople had a puzzle about them. Plan a walk that crosses every bridge exactly once. You may start wherever you like and finish wherever you like, but no bridge may be skipped and none may be crossed twice.

The walker in the animation starts on the north bank and does well for a while. After six bridges every bridge that touches the eastern land has been used, the walker is standing on it, and one bridge on the far side of the map has not been crossed.

Pause and try

Copy the map and try for yourself, with a different starting point and different choices. What is the largest number of bridges you can cross before you are stuck?

Show answerHide answer

Six is the most that anyone manages, and many attempts stop at five. For instance, calling the north bank $A$, the south bank $B$, the island $C$ and the eastern land $D$, the walk $A, C, B, D, C, A, D$ crosses six bridges and leaves one of the bridges between $B$ and $C$ unused.

Failing many times does not show that the task is impossible, because a clever route might be hiding among the thousands not yet tried. The people of Königsberg suspected there was no such walk and could not say why. In 1736 Leonhard Euler could, and the idea he used is usually counted as the first theorem of a subject called graph theory.

Throwing away the map

Most of the map has nothing to do with the puzzle. The lengths of the bridges and the shape of the island can change without changing which walks are possible. The only facts that matter are which pieces of land there are and which bridges join which. So shrink each piece of land to a dot and stretch each bridge into a line between two dots.

Each of the four land masses shrinks to a dot and each of the seven bridges stretches into a line joining two dots. Everything the puzzle depends on survives: 4 vertices and 7 edges.
Monocurl source
let Morph = |t| block {
    let pos = map(range(0, 4), |i| lerp(MC[i], KG[i], t))
    . Water(0.22 * (1 - t))
    if (t < 0.96) {
        . Lands(pos, 1 - t)
    }
    for (i in range(0, 7)) {
        let goal = edge_pts(KG, KE[i])
        let n = len(goal)
        . Path(
            map(range(0, n), |j| lerp(lerp(BR[i][0], BR[i][1], j / (n - 1)), goal[j], t)),
            lerp(BRIDGE_COL, SLATE, t),
            lerp(BRIDGE_W, 3.5, t)
        )
    }
    . Verts(pos, KN)
}
mesh scene = Morph(t: 0)
mesh words = []

slide "morph"
    play Wait(1.2)
    scene.t = 1
    play Lerp(4.5, [&scene])
    words = [
        center{[2.6, 0.95, 0]} Cap("4 land masses", 0.8, AZURE),
        center{[2.6, 0.45, 0]} Cap("become 4 vertices", 0.8, AZURE),
        center{[2.6, -0.45, 0]} Cap("7 bridges", 0.8, SLATE),
        center{[2.6, -0.95, 0]} Cap("become 7 edges", 0.8, SLATE)
    ]
    play Fade(0.6, [&words])
    play Wait(2.2)

Uses the shared definitions for this lesson's figures.

A picture of this kind is called a graph.

In the Königsberg graph the vertices are $A$, $B$, $C$, $D$ and the edges are the seven bridges. Two edges join $A$ to $C$ and two join $B$ to $C$, which is why the definition allows repeated edges. The puzzle now reads: trace along the edges, from vertex to vertex, so that every edge is traced exactly once.

On the graph it is easy to run several attempts and compare them.

  1. Start at A. After six edges the walker is at D and all three edges at D are used. One edge is left over.
  2. Start at C. This time the walker is stuck at D after five edges, with two edges left over.
  3. Start at D. The walker uses all five edges at C and is stuck there, with two edges left over.
Three attempts on the Königsberg graph, from three different starting vertices. Each one runs out of unused edges at some vertex while other edges, shown in red, are still untraced.
Monocurl source
let ATTEMPTS = [
    [[0, 0], [2, 1], [5, 0], [6, 1], [1, 1], [4, 0]],
    [[0, 1], [4, 0], [6, 1], [2, 1], [5, 0]],
    [[6, 1], [0, 1], [1, 0], [2, 1], [3, 0]]
]
let ROUTES = map(ATTEMPTS, |a| route(KG, KE, a))
let Line = |k, u| TrailLine(ROUTES[k], u)
let Dot = |k, u| Mover(ROUTES[k], u)
let unused = |k| filter(range(0, 7), |e| count(ATTEMPTS[k], |st| st[0] == e) == 0)
let last = |k| step_to(KE, ATTEMPTS[k][len(ATTEMPTS[k]) - 1])
let Row = |k| center{[2.6, 0.7 - k * 0.8, 0]} Cap(str_join([
    "from ", KN[step_from(KE, ATTEMPTS[k][0])], ": ", to_string(len(ATTEMPTS[k])), " of 7"
]), 0.85, INK)
let Stuck = |k| [
    map(unused(k), |e| Path(edge_pts(KG, KE[e]), ROSE, 5)),
    Disc(KG[last(k)], 0.37, ROSE, 0.45, 2)
]
mesh edges = Edges(KG, KE)
mesh stuck = []
mesh trail = []
mesh verts = Verts(KG, KN)
mesh dot = []
mesh panel = center{[2.6, 1.5, 0]} Note("edges traced", 0.76)
mesh rows = []

slide "Start at A. After six edges the walker is at D and all three edges at D are used. One edge is left over."
    trail = Line(0, u: 0)
    dot = Dot(0, u: 0)
    play Fade(0.4)
    trail.u = 1
    dot.u = 1
    play Lerp(3.6, [&trail, &dot], linear)
    dot = []
    stuck = Stuck(0)
    rows .= Row(0)
    play Fade(0.5)

slide "Start at C. This time the walker is stuck at D after five edges, with two edges left over."
    trail = []
    stuck = []
    play Fade(0.4)
    trail = Line(1, u: 0)
    dot = Dot(1, u: 0)
    play Fade(0.3)
    trail.u = 1
    dot.u = 1
    play Lerp(3.2, [&trail, &dot], linear)
    dot = []
    stuck = Stuck(1)
    rows .= Row(1)
    play Fade(0.5)

slide "Start at D. The walker uses all five edges at C and is stuck there, with two edges left over."
    trail = []
    stuck = []
    play Fade(0.4)
    trail = Line(2, u: 0)
    dot = Dot(2, u: 0)
    play Fade(0.3)
    trail.u = 1
    dot.u = 1
    play Lerp(3.2, [&trail, &dot], linear)
    dot = []
    stuck = Stuck(2)
    rows .= Row(2)
    play Fade(0.5)

Uses the shared definitions for this lesson's figures.

Why an odd vertex is trouble

Look at where the three attempts died. Twice the walker was stranded at $D$, a vertex with three edges, and once at $C$, a vertex with five. To see why that keeps happening, forget the rest of the graph and watch a single vertex while a walk passes through it.

  1. A walker in the middle of a trail arrives at the vertex. That uses one of its edges.
  2. The walker must leave along a different edge. One visit has used two edges.
  3. The walker comes back. At the vertex of degree 3 that uses the last edge, and there is no way out: the trail has to end here.
  4. At the vertex of degree 4 the walker leaves again. Two visits used two edges each, and no edge is left over.
One vertex with 3 edges and one with 4, watched while a walk passes through. Each visit uses two edges, one to arrive and one to leave. With 3 edges the second visit cannot leave. With 4 edges it can.
Monocurl source
let CL = [-1.95, 0.25, 0]
let CR = [1.95, 0.25, 0]
let LEN = 1.3
let far = |c, deg, j| c + [cos([PI * 7 / 6, PI / 4][deg == 4] + TAU * j / deg), sin([PI * 7 / 6, PI / 4][deg == 4] + TAU * j / deg), 0] * LEN
let Star = |c, deg| [
    map(range(0, deg), |j| [Seg(c, far(c, deg, j), SLATE, 3.5), Pt(far(c, deg, j), SLATE, 0.09)]),
    Disc(c, 0.22, AZURE, 0.2, 3)
]
# arrow number j (from 0) of a visit sequence: even ones arrive, odd ones leave
let Visit = |c, deg, j| {
    let f = far(c, deg, j)
    let side = normalize([-(f[1] - c[1]), f[0] - c[0], 0]) * 0.27
    return [
        [Tip(lerp(c, f, 0.9), lerp(c, f, 0.27), EMBER, 5, 0.22), Tip(lerp(c, f, 0.24), lerp(c, f, 0.9), EMBER, 5, 0.22)][mod(j, 2)],
        center{lerp(c, f, 0.62) + side} Math(to_string(j + 1), 0.85, EMBER)
    ]
}
mesh stars = [Star(CL, 3), Star(CR, 4)]
mesh names = [
    center{[CL[0], -1.3, 0]} Note("a vertex of degree 3", 0.76),
    center{[CR[0], -1.3, 0]} Note("a vertex of degree 4", 0.76)
]
mesh arrows = []
mesh verdict = []

slide "A walker in the middle of a trail arrives at the vertex. That uses one of its edges."
    arrows = [Visit(CL, 3, 0), Visit(CR, 4, 0)]
    play Fade(0.6)

slide "The walker must leave along a different edge. One visit has used two edges."
    arrows .= [Visit(CL, 3, 1), Visit(CR, 4, 1)]
    play Fade(0.6)

slide "The walker comes back. At the vertex of degree 3 that uses the last edge, and there is no way out: the trail has to end here."
    arrows .= [Visit(CL, 3, 2), Visit(CR, 4, 2)]
    verdict = [center{[CL[0], -1.85, 0]} Cap("3 = 2 + 1: stuck", 0.85, ROSE)]
    play Fade(0.6)

slide "At the vertex of degree 4 the walker leaves again. Two visits used two edges each, and no edge is left over."
    arrows .= Visit(CR, 4, 3)
    verdict .= center{[CR[0], -1.85, 0]} Cap("4 = 2 + 2: passed through", 0.85, LEAF)
    play Fade(0.6)

Uses the shared definitions for this lesson's figures.

Each visit to a vertex consumes two of its edges, one coming in and one going out. To build a proof on that we need names for the kinds of route.

The Königsberg puzzle asks for an Euler trail. The stricter version, in which you must also finish where you started, asks for an Euler circuit.

Now count the edges at each vertex of the Königsberg graph.

The Königsberg graph with the degree of each vertex. Each edge has two ends, marked by dots, so the degrees add up to twice the number of edges. All four degrees are odd.
The Königsberg graph with the degree of each vertex. Each edge has two ends, marked by dots, so the degrees add up to twice the number of edges. All four degrees are odd.
Monocurl source
let OFF = [[0.45, 0.3, 0], [0.45, -0.3, 0], [-0.3, 0.5, 0], [0.3, 0.5, 0]]
let DEGS = map(range(0, 4), |i| degree(KE, i))
mesh graph = [
    Edges(KG, KE),
    map(KE, |e| [Pt(edge_pts(KG, e)[4], EMBER, 0.075), Pt(edge_pts(KG, e)[20], EMBER, 0.075)]),
    Verts(KG, KN)
]
mesh degs = map(range(0, 4), |i| center{KG[i] + OFF[i]} Math(to_string(DEGS[i]), 1.05, EMBER))
mesh panel = [
    center{[2.6, 1.5, 0]} Note("sum of the degrees", 0.76),
    center{[2.6, 0.9, 0]} Math(str_join([str_join(map(DEGS, |d| to_string(d)), " + "), " = ", to_string(sum(DEGS))]), 0.85, EMBER),
    center{[2.6, 0.25, 0]} Math(str_join(["= 2 \times ", to_string(len(KE))]), 0.85, EMBER),
    center{[2.6, -0.6, 0]} Cap("two ends per edge", 0.8, INK),
    center{[2.6, -1.4, 0]} Cap("all four are odd", 0.85, ROSE)
]

Uses the shared definitions for this lesson's figures.

The degrees are 3, 3, 5 and 3. An Euler circuit would need all four to be even, and an Euler trail that is not closed would need exactly two to be odd. Königsberg has four odd vertices, so it has neither. The puzzle has no solution, and we know it without examining a single route.

The handshake lemma

Could a town be built with exactly one odd vertex? Or three? The figure above suggests a constraint: the degrees added up to 14, which is twice the number of bridges.

Pause and try

Draw any graph you like, with five or six vertices, and add up its degrees. Compare with the number of edges. Then explain what you see.

Show answerHide answer

The sum is always twice the number of edges. Each edge has two ends, and the degree of a vertex counts the edge ends that sit at it. Adding the degrees therefore counts every edge end once, which counts every edge twice.

The name comes from parties, with guests as vertices and handshakes as edges. For us it means that the possible counts of odd vertices are 0, 2, 4 and so on. Zero and two are the cases the necessity theorem leaves open.

Pause and try

Each of the three figures below is to be drawn with one stroke of the pen, without lifting it and without going over any line twice. In each figure a crossing of two lines in the middle is not a vertex. Count degrees and predict: which figures are impossible? For the others, where would you have to start?

Show answerHide answer

(a) The two bottom corners have degree 3, the two upper corners have degree 4 and the top has degree 2. With exactly two odd vertices a closed tour is impossible, and an open one would have to start at one bottom corner and end at the other. (b) All four corners have degree 3, so it is impossible. (c) All five vertices have degree 4, so nothing we have proved forbids it. Whether figures (a) and (c) can in fact be drawn is the next question.

Three figures to draw without lifting the pen: (a) a house with both diagonals, (b) a square with both diagonals, (c) a pentagon with all five diagonals.
Three figures to draw without lifting the pen: (a) a house with both diagonals, (b) a square with both diagonals, (c) a pentagon with all five diagonals.
Monocurl source
let HOUSE = [[-3.55, -1.2, 0], [-1.75, -1.2, 0], [-1.75, 0.55, 0], [-3.55, 0.55, 0], [-2.65, 1.65, 0]]
let HE = [[0, 1], [1, 2], [2, 3], [3, 0], [0, 2], [1, 3], [3, 4], [4, 2]]
let SQ = map(range(0, 4), |i| [-0.15 + 1.45 * cos(PI / 4 + i * PI / 2), 0.1 + 1.45 * sin(PI / 4 + i * PI / 2), 0])
let PENT = map(range(0, 5), |i| [2.5 + 1.18 * cos(PI / 2 + i * TAU / 5), 0.2 + 1.3 * sin(PI / 2 + i * TAU / 5), 0])
let all_pairs = |n| block {
    for (i in range(0, n)) {
        for (j in range(i + 1, n)) {
            . [i, j]
        }
    }
}
let Fig = |P, E| [
    map(E, |e| Seg(P[e[0]], P[e[1]], INK, 3.5)),
    map(P, |p| Pt(p, AZURE, 0.12))
]
mesh figs = [Fig(HOUSE, HE), Fig(SQ, all_pairs(4)), Fig(PENT, all_pairs(5))]
mesh names = [
    center{[-2.65, -1.8, 0]} Cap("(a)", 0.9, SLATE),
    center{[-0.15, -1.8, 0]} Cap("(b)", 0.9, SLATE),
    center{[2.5, -1.8, 0]} Cap("(c)", 0.9, SLATE)
]

Uses the shared definitions for this lesson's figures.

Even degrees are enough, in a connected graph

So far the theorem only forbids. It says that odd vertices spoil a tour, and it does not say that a graph without odd vertices has one. In fact there is one more way to fail.

Left: two separate triangles. Every degree is 2, but no trail can get from one triangle to the other. Right: the same triangles sharing a vertex. The graph is connected, and it has an Euler circuit.
Left: two separate triangles. Every degree is 2, but no trail can get from one triangle to the other. Right: the same triangles sharing a vertex. The graph is connected, and it has an Euler circuit.
Monocurl source
let TL = [[-3.5, 1.05, 0], [-3.5, -1.05, 0], [-2.35, 0, 0], [-0.5, 1.05, 0], [-0.5, -1.05, 0], [-1.65, 0, 0]]
let TE6 = [[0, 1], [1, 2], [2, 0], [3, 4], [4, 5], [5, 3]]
let TR = [[0.6, 1.05, 0], [0.6, -1.05, 0], [2.1, 0, 0], [3.6, 1.05, 0], [3.6, -1.05, 0]]
let TE5 = [[0, 1], [1, 2], [2, 0], [3, 4], [4, 2], [2, 3]]
let Marked = |P, E, col, offs| [
    map(E, |e| Seg(P[e[0]], P[e[1]], col, 3.5)),
    map(range(0, len(P)), |i| [
        Pt(P[i], col, 0.12),
        center{P[i] + offs[i]} Math(to_string(count(E, |e| e[0] == i or e[1] == i)), 0.85, SLATE)
    ])
]
mesh left = [
    Marked(TL, TE6, ROSE, [[0.05, 0.38, 0], [0.05, -0.38, 0], [0.05, 0.4, 0], [-0.05, 0.38, 0], [-0.05, -0.38, 0], [-0.05, 0.4, 0]]),
    center{[-2.0, 1.85, 0]} Cap("all even, not connected", 0.8, INK),
    center{[-2.0, -1.85, 0]} Cap("no Euler circuit", 0.85, ROSE)
]
mesh right = [
    Marked(TR, TE5, LEAF, [[0.05, 0.38, 0], [0.05, -0.38, 0], [0, 0.48, 0], [-0.05, 0.38, 0], [-0.05, -0.38, 0]]),
    center{[2.1, 1.85, 0]} Cap("all even, connected", 0.8, INK),
    center{[2.1, -1.85, 0]} Cap("Euler circuit", 0.85, LEAF)
]

Uses the shared definitions for this lesson's figures.

A graph with an Euler trail and no vertices of degree 0 is certainly connected, since the trail itself visits every vertex. The surprise is that connectedness and even degrees together are sufficient. To find out why, take a connected graph with all degrees even and try the most naive plan: start somewhere and keep walking along unused edges until you cannot.

Pause and try

The graph in the next figure is a triangle with its three midpoints joined. Starting at the corner $P$, a walker goes around the outside, $P, U, Q, V, R, W, P$, and is stuck at $P$ with three edges untraced. Before stepping through the figure, find a way to enlarge this circuit so that it takes in the three missing edges.

Show answerHide answer

The missing edges form the small triangle $U, V, W, U$, which is itself a circuit, and it touches the big circuit at $U$. On reaching $U$ for the first time, go around the small triangle and return to $U$, then carry on as before: $P, U, V, W, U, Q, V, R, W, P$. All nine edges are used once each.

  1. Every vertex has even degree: 2 at the corners P, Q, R and 4 at the midpoints U, V, W.
  2. Start at P and keep walking along unused edges. The walk stops only when it is back at P, after 6 edges.
  3. Three edges are unused, and they touch the circuit at U. A new walk from U along unused edges comes back to U.
  4. Splice them: follow the first circuit to U, go around the second, then finish the first. One circuit uses all 9 edges.
Building an Euler circuit by splicing. A first circuit from P stops after 6 edges. The leftover edges form a second circuit through U. Inserting the second circuit into the first at U gives one circuit through all 9 edges.
Monocurl source
let TP = [[-3.3, -1.6, 0], [0.9, -1.6, 0], [-1.2, 1.7, 0], [-1.2, -1.6, 0], [-0.15, 0.05, 0], [-2.25, 0.05, 0]]
let TN = ["P", "Q", "R", "U", "V", "W"]
let TE = [[0, 3, 0], [3, 1, 0], [1, 4, 0], [4, 2, 0], [2, 5, 0], [5, 0, 0], [3, 4, 0], [4, 5, 0], [5, 3, 0]]
let FIRST = [[0, 0], [1, 0], [2, 0], [3, 0], [4, 0], [5, 0]]
let SECOND = [[6, 0], [7, 0], [8, 0]]
let BOTH = [[0, 0], [6, 0], [7, 0], [8, 0], [1, 0], [2, 0], [3, 0], [4, 0], [5, 0]]
let R1 = route(TP, TE, FIRST)
let R2 = route(TP, TE, SECOND)
let R3 = route(TP, TE, BOTH)
mesh edges = Edges(TP, TE)
mesh one = []
mesh two = []
mesh all = []
mesh verts = Verts(TP, TN)
mesh dot = []
let PX = 2.48
let slot_y = |k| 0.22 - 0.9 * k
let Slot = |k, col = MIST| center{[PX, slot_y(k), 0]} fill{alpha{0} PAPER} stroke{col, 2} Rect([2.3, 0.46])
mesh panel = [
    center{[PX, 1.85, 0]} Note("degrees of", 0.8),
    center{[PX, 1.45, 0]} Math(str_join(TN, ",\ "), 0.9, AZURE),
    center{[PX, 1.05, 0]} Math(str_join(map(range(0, 6), |i| to_string(degree(TE, i))), ",\ "), 0.9, SLATE),
    map(range(0, 3), |k| Slot(k)),
    center{[PX, slot_y(0) + 0.39, 0]} Note("first circuit", 0.72),
    center{[PX, slot_y(1) + 0.39, 0]} Note("second circuit", 0.72)
]
mesh last = center{[PX, slot_y(2) + 0.39, 0]} Note("spliced circuit", 0.72)

slide "Every vertex has even degree: 2 at the corners P, Q, R and 4 at the midpoints U, V, W."
    play Fade(0.6)

slide "Start at P and keep walking along unused edges. The walk stops only when it is back at P, after 6 edges."
    one = TrailLine(R1, u: 0)
    dot = Mover(R1, u: 0)
    play Fade(0.3)
    one.u = 1
    dot.u = 1
    play Lerp(3.2, [&one, &dot], linear)
    dot = []
    panel .= center{[PX, slot_y(0), 0]} Math(seq_tex(TE, TN, FIRST), 0.9, EMBER)
    play Fade(0.5)

slide "Three edges are unused, and they touch the circuit at U. A new walk from U along unused edges comes back to U."
    two = TrailLine(R2, u: 0, PLUM)
    dot = Mover(R2, u: 0, PLUM)
    play Fade(0.3)
    two.u = 1
    dot.u = 1
    play Lerp(2.2, [&two, &dot], linear)
    dot = []
    panel .= center{[PX, slot_y(1), 0]} Math(seq_tex(TE, TN, SECOND), 0.9, PLUM)
    play Fade(0.5)

slide "Splice them: follow the first circuit to U, go around the second, then finish the first. One circuit uses all 9 edges."
    one = []
    two = []
    last = []
    play Fade(0.4)
    all = TrailLine(R3, u: 0, LEAF)
    dot = Mover(R3, u: 0, LEAF)
    play Fade(0.3)
    all.u = 1
    dot.u = 1
    play Lerp(4.5, [&all, &dot], linear)
    dot = []
    panel .= center{[PX, slot_y(2), 0]} Math(seq_tex(TE, TN, BOTH), 0.78, LEAF)
    last = center{[PX, slot_y(2) + 0.39, 0]} Cap(str_join(["spliced: all ", to_string(len(BOTH)), " edges"]), 0.74, LEAF)
    play Fade(0.5)

Uses the shared definitions for this lesson's figures.

Two things happened in that figure, and each needs a reason. The walker got stuck only at the starting vertex, which is the in and out count again. And the leftover edges touched the circuit somewhere, which is where connectedness is used.

Euler proved the “only if” halves and stated the rest. The first published proof of the “if” halves is due to Carl Hierholzer, and it is the splicing argument. The “only if” halves are the necessity theorem above, so what remains is to construct the tours. We start with part 1.

Compare the proof with the figure. Step 1 is the walker returning to $P$. The walk from $c$ to $x$ is how we know the leftover triangle must touch the first circuit, and for the two separate triangles no such walk exists, which is exactly where the proof breaks for them.

Two odd vertices: an open trail

Part 2 follows from part 1 by a trick worth remembering: change the problem into one already solved. Take figure (a), the house, whose two bottom corners are odd.

  1. The bottom corners a and b have degree 3. The other three vertices have even degree.
  2. Add one new edge from a to b. Both odd degrees go up by one, so every degree is now even.
  3. By part 1 the enlarged graph has an Euler circuit. This one starts by crossing the new edge from a to b.
  4. Delete the new edge. The rest of the circuit is a trail from b to a that uses each of the 8 original edges once.
An Euler trail in the house. An extra edge between the two odd vertices makes every degree even, so there is an Euler circuit. Deleting the extra edge opens the circuit into a trail from one odd vertex to the other.
Monocurl source
let HP = [[-3.1, -1.25, 0], [-0.3, -1.25, 0], [-0.3, 0.5, 0], [-3.1, 0.5, 0], [-1.7, 1.7, 0]]
let HN = ["a", "b", "c", "d", "e"]
let HE = [[0, 1, 0], [1, 2, 0], [2, 3, 0], [3, 0, 0], [0, 2, 0], [1, 3, 0], [3, 4, 0], [4, 2, 0]]
let HX = flatten([HE, [[0, 1, -0.3]]])
let OFF = [[-0.47, 0, 0], [0.47, 0, 0], [0.47, 0, 0], [-0.47, 0, 0], [0.47, 0.05, 0]]
let CIRCUIT = [[8, 0], [1, 0], [7, 1], [6, 1], [3, 0], [4, 0], [2, 0], [5, 1], [0, 1]]
let OPEN = drop(CIRCUIT, 1)
let RC = route(HP, HX, CIRCUIT)
let RO = route(HP, HX, OPEN)
let Degs = |E| map(range(0, 5), |i| center{HP[i] + OFF[i]} Math(to_string(degree(E, i)), 0.9, [SLATE, ROSE][mod(degree(E, i), 2)]))
mesh extra = []
mesh edges = Edges(HP, HE)
mesh trail = []
mesh verts = Verts(HP, HN)
mesh dot = []
mesh degs = Degs(HE)
let PX = 2.25
let slot_y = |k| 1.4 - 0.97 * k
let odd = |d| d - 2 * (d // 2) == 1
mesh panel = [
    map(range(0, 4), |k| center{[PX, slot_y(k), 0]} fill{alpha{0} PAPER} stroke{MIST, 2} Rect([2.6, 0.48])),
    center{[PX, slot_y(0) + 0.4, 0]} Note("odd vertices", 0.72),
    center{[PX, slot_y(1) + 0.4, 0]} Note("add an edge from a to b", 0.72),
    center{[PX, slot_y(2) + 0.4, 0]} Note("Euler circuit", 0.72),
    center{[PX, slot_y(3) + 0.4, 0]} Note("delete the new edge", 0.72),
    center{[PX, slot_y(0), 0]} Math(str_join(map(filter(range(0, 5), |i| odd(degree(HE, i))), |i| HN[i]), ",\ "), 0.95, ROSE)
]

slide "The bottom corners a and b have degree 3. The other three vertices have even degree."
    play Fade(0.6)

slide "Add one new edge from a to b. Both odd degrees go up by one, so every degree is now even."
    degs = []
    play Fade(0.3, [&degs])
    extra = Path(edge_pts(HP, HX[8]), PLUM, 4)
    degs = Degs(HX)
    panel .= center{[PX, slot_y(1), 0]} Cap("every degree even", 0.8, PLUM)
    play Fade(0.6)

slide "By part 1 the enlarged graph has an Euler circuit. This one starts by crossing the new edge from a to b."
    trail = TrailLine(RC, u: 0)
    dot = Mover(RC, u: 0)
    play Fade(0.3)
    trail.u = 1
    dot.u = 1
    play Lerp(4.5, [&trail, &dot], linear)
    dot = []
    panel .= center{[PX, slot_y(2), 0]} Math(seq_tex(HX, HN, CIRCUIT), 0.95, EMBER)
    play Fade(0.5)

slide "Delete the new edge. The rest of the circuit is a trail from b to a that uses each of the 8 original edges once."
    trail = []
    extra = []
    degs = []
    play Fade(0.4)
    trail = TrailLine(RO, u: 1, LEAF)
    degs = Degs(HE)
    panel .= center{[PX, slot_y(3), 0]} Math(seq_tex(HX, HN, OPEN), 0.95, LEAF)
    play Fade(0.6)

Uses the shared definitions for this lesson's figures.

So figure (a) can be drawn, from one bottom corner to the other and in no other way. Figure (c) is connected with all degrees 4, so it has an Euler circuit starting anywhere. Numbering its vertices 1 to 5 around the pentagon, one such circuit is $1, 2, 3, 4, 5, 1, 3, 5, 2, 4, 1$: the outside first, then the star.

Worked examples

Common mistakes

Stepping back

The hard part of the Königsberg puzzle was that the number of possible routes is large and trying them teaches nothing. Euler’s move was to stop looking at routes and look at one vertex at a time, where a route has to pair off the edges. That turned a search into a count. The same count now decides the question for any map, any pen-and-paper figure and a set of dominoes.

Proving that something cannot be done by finding a quantity that every attempt must respect, here the parity of a degree, is a method you will meet again. The pigeonhole principle is another example of a count that forces a conclusion. Part 1 can also be proved by induction on the number of edges: remove a circuit and apply the hypothesis to the pieces that remain.

Check yourself

A connected graph has six vertices, with degrees 2, 2, 3, 3, 4, 4. What can you conclude?

Problems

Problems 1 to 3 apply the theorem directly. Problems 4 and 5 combine it with other counts. Problems 6 and 7 extend the lesson.

Problem 1

A graph has vertices $a, b, c, d, e, f$ and edges $ab, bc, cd, de, ef, fa, ac, ce, ea$. Find the degree of each vertex, check the handshake lemma, and decide whether the graph has an Euler circuit. If it does, write one down.

Show solutionHide solution

Counting appearances in the list: $a$ is in $ab, fa, ac, ea$, so $\deg(a) = 4$. Likewise $\deg(c) = 4$ (from $bc, cd, ac, ce$) and $\deg(e) = 4$ (from $de, ef, ce, ea$), while $b$, $d$ and $f$ have degree 2. The sum is $4 + 4 + 4 + 2 + 2 + 2 = 18 = 2 \times 9$. The graph is connected, since $ab, bc, cd, de, ef$ link all six vertices, and every degree is even, so there is an Euler circuit. Going around the hexagon and then the triangle: $a, b, c, d, e, f, a, c, e, a$. This uses $ab, bc, cd, de, ef, fa, ac, ce, ea$, all nine edges.

Problem 2

Two of the seven bridges were destroyed in the Second World War and others were rebuilt, and by the usual account five bridges now stand on the old sites: one from each bank to the island $C$, one from each bank to the eastern land $D$, and one between $C$ and $D$. Is the walk possible today? Where can it start?

Show solutionHide solution

The edges are $AC, BC, AD, BD, CD$. The degrees are $\deg(A) = 2$, $\deg(B) = 2$, $\deg(C) = 3$, $\deg(D) = 3$, with sum $10 = 2 \times 5$. The graph is connected and exactly two vertices are odd, so by part 2 there is an Euler trail, and it must start at $C$ or $D$ and end at the other. One such walk is $C, A, D, B, C, D$. A walk that returns to its starting point is still impossible.

Problem 3

Go back to the original seven bridges. What is the smallest number of new bridges the town would have had to build to make possible (a) a walk crossing every bridge once, (b) such a walk that also returns to its start?

Show solutionHide solution

All four vertices are odd. A new bridge raises the degree of each of its two ends by one, so it changes the parity of exactly two vertices. (a) We need at most two odd vertices. Zero new bridges leave four, so at least one is needed, and one is enough: a bridge from $A$ to $B$ makes the degrees 4, 4, 5, 3, and the walk then runs between $C$ and $D$. (b) We need all four vertices to become even. One bridge changes only two parities, so at least two bridges are needed. Two are enough: bridges from $A$ to $B$ and from $C$ to $D$ give degrees 4, 4, 6, 4, all even, and the graph is still connected.

Problem 4

The complete graph $K_n$ has $n$ vertices and exactly one edge between every two of them. For which $n \ge 2$ does $K_n$ have an Euler circuit? For which $n$ does it have an Euler trail that is not closed?

Show solutionHide solution

$K_n$ is connected, and each vertex is joined to the other $n - 1$, so every degree is $n - 1$. There is an Euler circuit exactly when $n - 1$ is even, that is, when $n$ is odd (for instance $K_5$, figure (c) of the lesson). When $n$ is even all $n$ vertices are odd, and an open Euler trail needs exactly two odd vertices, which happens only for $n = 2$. Indeed $K_2$ is a single edge, which is its own Euler trail, while $K_4$, figure (b), has four odd vertices and no Euler trail.

Problem 5

(a) Nine people are in a room. Is it possible that each of them is friends with exactly three of the others? (Friendship is mutual.) (b) What if there are eight people?

Show solutionHide solution

(a) Make the people vertices and each friendship an edge. Every degree would be 3, so the sum of the degrees would be $9 \times 3 = 27$. By the handshake lemma the sum is twice the number of edges, which is even, and 27 is odd. So it is impossible. (b) The sum would be $8 \times 3 = 24$, so the lemma raises no objection, with 12 friendships. An example confirms it: put the eight people at the corners of a cube and let the 12 edges of the cube be the friendships. Each corner of a cube lies on exactly three edges.

Problem 6

A student claims: “In a connected graph with all degrees even, start anywhere and keep walking along unused edges. You can only get stuck at your starting vertex, so when you stop you have an Euler circuit.” Show by an example with six edges that this is wrong, and say which part of the claim is true.

Show solutionHide solution

Take two triangles that share one vertex $u$: the first has vertices $u, x, y$ and the second $u, s, t$. The degrees are 4 at $u$ and 2 elsewhere, and the graph is connected. Start at $x$ and walk $x, u, y, x$. Every step uses an unused edge, and the walker is now at $x$ with both edges at $x$ used, so the walk stops after 3 of the 6 edges. The true part is that the walk can only stop at its starting vertex (Step 1 of the proof). The false part is the conclusion that all edges have been used by then. The repair is the splice: the unused triangle $u, s, t, u$ is inserted when the walk first reaches $u$, giving $x, u, s, t, u, y, x$.

Problem 7

A connected graph has exactly $2k$ vertices of odd degree, where $k \ge 1$. Prove that its edges can be split among $k$ trails, with every edge on exactly one trail, and that fewer than $k$ trails never suffice. How many strokes of the pen does the Königsberg graph need?

Show solutionHide solution

$k$ trails suffice. Pair up the odd vertices as $x_1, y_1$, then $x_2, y_2$, up to $x_k, y_k$, and add a new edge joining each pair. Every odd degree goes up by one, so the enlarged graph has all degrees even, and it is still connected. By part 1 it has an Euler circuit. Delete the $k$ new edges from this circuit. They cut it into $k$ pieces, each a trail made of original edges. No piece is empty: two new edges never share an end, because the $2k$ odd vertices are all different, so two new edges are never consecutive on the circuit. Every original edge is on exactly one piece.

Fewer do not. Suppose the edges are split among some trails. Let $v$ be a vertex that is not the first or last vertex of any of them. Then each trail passes through $v$ using two edges at a time, as in the necessity proof, so $\deg(v)$ is even. Hence each of the $2k$ odd vertices is the first or last vertex of at least one trail. A trail has only two such positions, so there are at least $k$ trails.

Königsberg has four odd vertices, so $k = 2$: the seven bridges need two separate walks, for example $A, C, B, D, C, A, D$ (six bridges) and then the remaining bridge $B, C$.

Shared definitions used by this lesson's figures
# house kit: prepended to every essay figure after the std imports

# palette. AZURE and EMBER are the two actors, LEAF marks an answer, ROSE a problem
let PAPER = [1, 1, 1, 1]
let INK = [0.08, 0.09, 0.11, 1]
let SLATE = [0.40, 0.44, 0.50, 1]
let MIST = [0.85, 0.86, 0.88, 1]
let AZURE = [0.13, 0.42, 0.68, 1]
let EMBER = [0.89, 0.42, 0.12, 1]
let LEAF = [0.13, 0.55, 0.36, 1]
let ROSE = [0.78, 0.22, 0.24, 1]
let PLUM = [0.47, 0.30, 0.66, 1]
let GOLD = [0.85, 0.62, 0.08, 1]

background = PAPER

# an opaque pastel of col; use for fills so overlaps do not darken
let tint = |col, amount = 0.18| [
    1 - (1 - col[0]) * amount,
    1 - (1 - col[1]) * amount,
    1 - (1 - col[2]) * amount,
    1
]

# text: Cap for labels, Note for quiet labels, Math for typeset maths
let Cap = |txt, size = 0.75, col = INK| color{col} Text(txt, size, "Charter")
let Note = |txt, size = 0.65| color{SLATE} Text(txt, size, "Charter")
let Math = |tex, size = 0.9, col = INK| color{col} Tex(tex, size)

# a paper rectangle to sit behind text that crosses lines
let Plate = |pos, size| center{pos} fill{PAPER} stroke{alpha{0} PAPER, 0} Rect(size)

# shapes with fill and stroke both set, so no stray black appears
let Pt = |pos, col = INK, r = 0.075| center{pos} fill{col} stroke{col, 1} Circle(r, 48)
let Ring = |pos, col = INK, r = 0.1, w = 3| center{pos} fill{PAPER} stroke{col, w} Circle(r, 48)
let Seg = |a, b, col = INK, w = 3| stroke{col, w} Line(a, b)
let Path = |pts, col = INK, w = 3| stroke{col, w} Polyline(pts)
let Region = |pts, col| fill{col} stroke{alpha{0} col, 0} Polygon(pts)
let Outline = |pts, col = INK, w = 3| fill{alpha{0} PAPER} stroke{col, w} Polygon(pts)
let Shape = |pts, col, amount = 0.18, w = 3| fill{tint(col, amount)} stroke{col, w} Polygon(pts)
let Disc = |pos, r, col, amount = 0.18, w = 3| center{pos} fill{tint(col, amount)} stroke{col, w} Circle(r, 96)
let Box = |pos, size, col, amount = 0.18, w = 3| center{pos} fill{tint(col, amount)} stroke{col, w} Rect(size)
# a line from a to b ending in a small solid arrowhead
let Tip = |a, b, col = INK, w = 3, head = 0.17| {
    let d = normalize([b[0] - a[0], b[1] - a[1], 0])
    let n = [-d[1], d[0], 0]
    let base = [b[0] - d[0] * head, b[1] - d[1] * head, 0]
    return [
        stroke{col, w} Line(a, base),
        fill{col} stroke{col, 1} Polygon([
            b,
            [base[0] + n[0] * head * 0.42, base[1] + n[1] * head * 0.42, 0],
            [base[0] - n[0] * head * 0.42, base[1] - n[1] * head * 0.42, 0]
        ])
    ]
}

# plots. a frame is [x0, x1, y0, y1, centre, width, height]: a data window and the scene box it fills
let Frame = |x0, x1, y0, y1, c = [0, 0, 0], w = 6.4, h = 3.6| [x0, x1, y0, y1, c, w, h]
let fx = |fr, x| fr[4][0] + (x - (fr[0] + fr[1]) / 2) * fr[5] / (fr[1] - fr[0])
let fy = |fr, y| fr[4][1] + (y - (fr[2] + fr[3]) / 2) * fr[6] / (fr[3] - fr[2])
let at = |fr, x, y| [fx(fr, x), fy(fr, y), 0]

# axes through the data origin (or the nearest edge of the window), with optional names
let Axes = |fr, x_name = "", y_name = "", col = SLATE| {
    let ox = clamp(fr[0], 0, fr[1])
    let oy = clamp(fr[2], 0, fr[3])
    var parts = [
        Tip(at(fr, fr[0], oy), [fx(fr, fr[1]) + 0.25, fy(fr, oy), 0], col, 2.5, 0.15),
        Tip(at(fr, ox, fr[2]), [fx(fr, ox), fy(fr, fr[3]) + 0.25, 0], col, 2.5, 0.15)
    ]
    if (x_name != "") {
        parts .= center{[fx(fr, fr[1]) + 0.5, fy(fr, oy), 0]} Math(x_name, 0.8, col)
    }
    if (y_name != "") {
        parts .= center{[fx(fr, ox), fy(fr, fr[3]) + 0.5, 0]} Math(y_name, 0.8, col)
    }
    return parts
}

# tick marks with numbers at the listed data values
let XTicks = |fr, xs, col = SLATE, places = 0| block {
    let oy = fy(fr, clamp(fr[2], 0, fr[3]))
    for (x in xs) {
        . Seg([fx(fr, x), oy - 0.07, 0], [fx(fr, x), oy + 0.07, 0], col, 2)
        . center{[fx(fr, x), oy - 0.33, 0]} Math(to_string(x, places), 0.72, col)
    }
}
let YTicks = |fr, ys, col = SLATE, places = 0| block {
    let ox = fx(fr, clamp(fr[0], 0, fr[1]))
    for (y in ys) {
        . Seg([ox - 0.07, fy(fr, y), 0], [ox + 0.07, fy(fr, y), 0], col, 2)
        . center{[ox - 0.36, fy(fr, y), 0]} Math(to_string(y, places), 0.72, col)
    }
}

# faint grid lines at the listed data values
let GridLines = |fr, xs, ys, col = MIST| block {
    for (x in xs) {
        . Seg(at(fr, x, fr[2]), at(fr, x, fr[3]), col, 1.5)
    }
    for (y in ys) {
        . Seg(at(fr, fr[0], y), at(fr, fr[1], y), col, 1.5)
    }
}

# the graph of f over [a, b], drawn in the frame. values are clamped to the window
let Graph = |fr, f, a, b, col = AZURE, w = 4, n = 160|
    stroke{col, w} Polyline(map(sample(a, b, n), |x| at(fr, x, clamp(fr[2], f(x), fr[3]))))

# the region between the graph of f and the x axis over [a, b]
let AreaUnder = |fr, f, a, b, col = AZURE, amount = 0.2, n = 80| {
    var pts = [at(fr, a, 0)]
    for (x in sample(a, b, n)) {
        pts .= at(fr, x, clamp(fr[2], f(x), fr[3]))
    }
    pts .= at(fr, b, 0)
    return fill{tint(col, amount)} stroke{alpha{0} col, 0} Polygon(pts)
}

let dist = |p, q| sqrt((p[0] - q[0]) ^ 2 + (p[1] - q[1]) ^ 2)
# points of the curve from p to q whose middle is pushed sideways by bow
let curve = |p, q, bow = 0, n = 25| {
    let d = normalize([q[0] - p[0], q[1] - p[1], 0])
    let c = [(p[0] + q[0]) / 2 - d[1] * bow * 2, (p[1] + q[1]) / 2 + d[0] * bow * 2, 0]
    return map(sample(0, 1, n), |s| [
        (1 - s) ^ 2 * p[0] + 2 * s * (1 - s) * c[0] + s ^ 2 * q[0],
        (1 - s) ^ 2 * p[1] + 2 * s * (1 - s) * c[1] + s ^ 2 * q[1],
        0
    ])
}
# a graph is a list of vertex positions P and a list of edges E, each [u, v, bow]
let edge_pts = |P, e| curve(P[e[0]], P[e[1]], e[2])
let Edges = |P, E, col = SLATE, w = 3.5| map(E, |e| Path(edge_pts(P, e), col, w))
let Vert = |pos, name, col = AZURE, r = 0.25, size = 0.8| [
    Disc(pos, r, col, 0.2, 3),
    center{pos} Math(name, size, col)
]
let Verts = |P, names, col = AZURE, r = 0.25| map(range(0, len(P)), |i| Vert(P[i], names[i], col, r))
let degree = |E, v| count(E, |e| e[0] == v) + count(E, |e| e[1] == v)
# a step is [edge index, 0 forwards or 1 backwards]
let step_from = |E, st| E[st[0]][st[1]]
let step_to = |E, st| E[st[0]][1 - st[1]]
# one polyline that follows the listed steps
let route = |P, E, steps| block {
    for (k in range(0, len(steps))) {
        let pts = edge_pts(P, E[steps[k][0]])
        let run = [pts, reverse(pts)][steps[k][1]]
        for (j in range([1, 0][k == 0], len(run))) {
            . run[j]
        }
    }
}
# the names of the vertices visited by the listed steps, as TeX
let seq_tex = |E, names, steps| str_join(
    flatten([[names[step_from(E, steps[0])]], map(steps, |st| names[step_to(E, st)])]), "\,"
)
# the first fraction u of a polyline, and the point where it ends
let part = |pts, u| {
    let m = len(pts) - 1
    let x = clamp(0, u, 1) * m
    let k = to_int(min(floor(x), m - 1))
    var out = take(pts, k + 1)
    if (x - k > 0.02) {
        out .= lerp(pts[k], pts[k + 1], x - k)
    }
    return out
}
let tip_of = |pts, u| {
    let m = len(pts) - 1
    let x = clamp(0, u, 1) * m
    let k = to_int(min(floor(x), m - 1))
    return lerp(pts[k], pts[k + 1], x - k)
}
let TrailLine = |pts, u, col = EMBER, w = 5| block {
    if (u * (len(pts) - 1) > 1) {
        . Path(part(pts, u), col, w)
    }
}
let Mover = |pts, u, col = EMBER, r = 0.13| Pt(tip_of(pts, u), col, r)

# Königsberg. vertices: A north bank, B south bank, C the island, D the land to the east
let KN = ["A", "B", "C", "D"]
let KE = [[0, 2, -0.3], [0, 2, 0.3], [1, 2, 0.3], [1, 2, -0.3], [0, 3, 0], [1, 3, 0], [2, 3, 0]]
let KG = [[-1.1, 1.55, 0], [-1.1, -1.55, 0], [-3.0, 0, 0], [0.9, 0, 0]]
# the map: a meeting point on each land mass, and the two ends of each bridge
let MC = [[0, 1.5, 0], [0, -1.5, 0], [-1.6, 0, 0], [2.2, 0, 0]]
let BR = [
    [[-2.2, 1.25, 0], [-2.2, 0.5, 0]],
    [[-1.0, 1.25, 0], [-1.0, 0.5, 0]],
    [[-2.2, -1.25, 0], [-2.2, -0.5, 0]],
    [[-1.0, -1.25, 0], [-1.0, -0.5, 0]],
    [[2.2, 1.25, 0], [2.2, 0.5, 0]],
    [[2.2, -1.25, 0], [2.2, -0.5, 0]],
    [[-0.55, 0, 0], [0.4, 0, 0]]
]
let LANDS = [
    [[-4.2, 1.15, 0], [4.2, 1.15, 0], [4.2, 2.5, 0], [-4.2, 2.5, 0]],
    [[-4.2, -2.5, 0], [4.2, -2.5, 0], [4.2, -1.15, 0], [-4.2, -1.15, 0]],
    map(sample_clopen(0, TAU, 40), |a| [-1.6 + 1.15 * sign(cos(a)) * abs(cos(a)) ^ 0.6, 0.62 * sign(sin(a)) * abs(sin(a)) ^ 0.6, 0]),
    flatten([
        map(sample(PI / 2, 3 * PI / 2, 16), |a| [0.95 + 0.62 * cos(a), 0.62 * sin(a), 0]),
        [[4.2, -0.62, 0], [4.2, 0.62, 0]]
    ])
]
# each land mass drawn around pos[i], shrunk by the factor k
let Lands = |pos, k| map(range(0, 4), |i| Shape(map(LANDS[i], |p| pos[i] + (p - MC[i]) * k), GOLD, 0.3, 2))
let Water = |amount| center{[0, 0, 0]} fill{tint(AZURE, amount)} stroke{tint(AZURE, amount), 1} Rect([8.4, 4.8])
let BRIDGE_COL = [0.55, 0.58, 0.63, 1]
let BRIDGE_W = 20