A registrar schedules final exams for six courses: Algebra, Biology, Chemistry, Drama, Economics and French. Two exams cannot share a time slot if some student takes both courses. Here are the students' course lists:
| Student | Courses |
|---|---|
| Ana | A, B, C |
| Ben | C, D |
| Caro | D, E, F |
| Dev | A, F |
The registrar does not care who the students are, only which pairs of courses conflict. Draw a point for each course and a line between two courses whenever some student takes both:
That picture is a graph, and everything the registrar needs is in it: which exams may share a slot, and which may not. This module sets up the language: what a graph is, the families of graphs that come up again and again, and how to count their edges with proofs rather than pictures.
The direct way to build the conflict graph checks every pair of courses against every student:
from itertools import combinations
students = {"Ana": "ABC", "Ben": "CD", "Caro": "DEF", "Dev": "AF"}
courses = sorted(set("".join(students.values())))
def conflicts_by_pairs(students, courses):
return {(a, b) for a, b in combinations(courses, 2)
if any(a in taken and b in taken for taken in students.values())}
edges = conflicts_by_pairs(students, courses)
assert len(edges) == 8 and ("A", "F") in edges and ("B", "D") not in edges
With courses and students, that is checks: for a university with 3,000 courses and 30,000 students, over . Almost every check finds nothing, because each student takes only a few courses.
A student's list already says which conflicts that student creates: every pair of courses on it. So instead of asking, for each pair of courses, whether some student takes both, go through the students once and add each student's pairs.
Invariant
After the first students, the graph has exactly the conflicts that those students create.
Before any student there are no edges, and each student adds exactly their own pairs, so the statement stays true from one panel to the next. After the last student it says that the picture is the conflict graph. Ana's three courses make 3 pairs, Ben's two make 1, Caro's make 3 and Dev's make 1: eight pairs visited, against the checks of the first method.
from math import comb
visits = sum(comb(len(taken), 2) for taken in students.values())
assert visits == 8 and comb(len(courses), 2) * len(students) == 60
The lab asks you to write this construction for a different school.
A graph consists of a set of vertices and a set of edges; each edge has two endpoints. An edge with both endpoints equal is a loop, and two edges with the same pair of endpoints are multiple edges. A simple graph has neither, and then an edge is just an unordered pair of distinct vertices. In this course a graph is simple unless we say otherwise.
Some models need the general version. In 1736 Euler studied the seven bridges of Königsberg, which join four land masses: two land masses joined by two bridges need two edges between the same pair of vertices. Module 4 answers his question.
Vocabulary used from here on:
In Python the course stores a graph as a dict from each vertex to the set of its neighbors:
def network(edges):
g = {}
for u, v in edges:
g.setdefault(u, set()).add(v)
g.setdefault(v, set()).add(u)
return g
conflict = network(edges)
assert conflict["D"] == {"C", "E", "F"}
Not every graph is bipartite. However the three vertices of are split into two sets, two of them land in the same set (pigeonhole), and those two are adjacent. So is not bipartite.
Proposition. has edges, and has edges.
Proof. In every pair of distinct vertices is an edge, and every edge is such a pair: the map from edges to their sets of endpoints is a bijection onto the 2-element subsets of , and there are of those.
In every edge has exactly one endpoint in and one in , and every such pair is an edge. So the map sending an edge to its ordered pair (endpoint in , endpoint in ) is a bijection onto , which has elements.
Proof move used here. Bijection: count a set by matching it, one to one and onto, with a set you can already count. Read the definitions exactly: "every pair is an edge" gives onto, and "edges are pairs of distinct vertices" (the graph is simple) gives one to one.
In the conflict graph, Ana takes three courses, so she alone creates conflicts: a copy of .
At most . Compare that with the million pairs of courses the first method checks. Where the number comes from, and when a school reaches it exactly, is a proof you assemble in the lab.
The complement of a simple graph has the same vertices, and is an edge of exactly when it is not an edge of . For the registrar, the complement of the conflict graph shows which exams may share a slot.
Proposition. If has vertices, then .
Proof. Each of the pairs of distinct vertices is an edge of exactly one of and , so the two edge sets split the pairs between them.
For the four students, and , so seven pairs of exams may share a slot.
Proof move used here. Decomposition: split the pairs into two classes, each pair in exactly one, and the two counts add up to the total.
A subgraph of is a graph with and . For a set of vertices, the induced subgraph keeps the vertices of and every edge of with both endpoints in .
In the conflict graph, the courses induce a triangle, because all three pairs conflict. The path (edges and ) is a subgraph of the conflict graph, but not an induced one: keeping the vertices and forces you to keep the edge .
A subgraph need not be: drop an edge of and you have a path. An induced subgraph of keeps every edge among its vertices, so it is complete.
Statements in this course lean on two phrases, and each one tells you what a proof owes.
"Exactly when" (also written "if and only if") makes two claims, and a proof owes both. Take the statement: a simple graph with vertices has edges exactly when it is complete. One direction is the proposition about . For the other, suppose has edges. Then by the complement count, so no pair of vertices is a non-edge of , and is complete. Proving only one direction proves a weaker statement.
"Every" makes a claim about all objects of some kind, and a single counterexample refutes it: an object that meets every hypothesis of the claim and still fails its conclusion. "Every subgraph of a complete graph is complete" is refuted by the path inside the triangle on from the last section. An object that misses a hypothesis refutes nothing, so check the hypotheses first.
Even at this level, claims about graphs come with evidence. "These two exams conflict" is proved by naming one student who takes both; "these two exams do not conflict" needs everyone's list. " is bipartite" is proved by showing the parts and , checked in one pass over the edges. The lab asks for evidence of this kind both ways.
A park ranger wants a hiking loop that starts and ends at the visitor center and walks every trail exactly once. Before marking any route on the map, she would like to know whether such a loop can exist at all, and what would prove that it cannot. Nothing in the question says "graph". Module 4 answers it; the lab's last exercise is another question of this kind.
The lab runs in your browser. Your code builds the conflict graph of another school one student at a time, drawing each step, and decides whether a piece of a graph is an induced subgraph, naming the vertex or edge that proves it either way. Half of the lab is proofs: assemble a proof about when the conflict count is as large as it can be, find the false step in an argument about bipartite graphs, and write a proof for a problem that names no technique.
nx.complement,
nx.complete_graph, nx.complete_bipartite_graph and G.subgraph(S) (which is induced) build
the objects of this module.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.