Graph vocabulary
The words used to talk about graphs: vertices, edges, degree, paths, walks, trails, cycles, components and connectedness, each with a precise meaning.
Learning goals
- Name the parts of a graph and count degrees, including in-degree and out-degree
- Tell walks, trails, paths and cycles apart by what may repeat
- Explain components and connectedness for undirected and directed graphs
- Turn each definition into a small Java check
Prerequisites
A road map, a friendship network and a build-dependency list look nothing alike, yet all are graphs: things and the connections between them. Precise words let you say exactly what a problem asks: a path, not just a walk; connected, not just reachable.
Vertices
A graph is a pair G = (V, E): a set V of **vertices** (also called nodes) and a set E of **edges** joining them. Vertices stand for the things: cities, people, tasks, board positions. In code they are almost always the integers 0 to n - 1, because an integer indexes an array. When the real things have names, translate each name to an integer once, at the boundary, and keep the table to translate back.
Open the animation and add the preset's edges one at a time: the graph has 5 vertices before any edge exists, and a vertex with no edge is still a vertex (it is an *isolated* vertex). The number of vertices is written n or |V|.
1class VertexIds { // map arbitrary names to the integers 0..n-12private final Map<String, Integer> id = new HashMap<>();3private final List<String> names = new ArrayList<>();4int idOf(String name) { // the first time a name is seen it gets the next number5return id.computeIfAbsent(name, k -> { names.add(k); return names.size() - 1; });6}7String nameOf(int v) { return names.get(v); }8int count() { return names.size(); }9}
One graph, three representations: matrix, adjacency list, edge list
Interactive · edit the inputgrid · 6 × 6
undirected graph · 5 vertices · 0 edges
list[0]
list[1]
list[2]
list[3]
list[4]
edge list (a-b, weight)
An empty undirected graph on 5 vertices. The three representations below are always updated together: the matrix always has 5 × 5 = 25 cells, the adjacency list grows with the edges, and the edge list is just the edges.
1int[][] matrix = new int[n][n]; // adjacency matrix: n * n cells2List<List<int[]>> list = new ArrayList<>(); // adjacency list: {neighbour, weight} per vertex3for (int i = 0; i < n; i++) list.add(new ArrayList<>());4int[][] edgeList = new int[m][]; // edge list: {a, b, weight}5void addEdge(int k, int a, int b, int w, boolean directed) {6edgeList[k] = new int[]{a, b, w};7matrix[a][b]++; // directed: this one cell is the whole edge8list.get(a).add(new int[]{b, w});9if (!directed) {10matrix[b][a]++; // undirected: also the mirror cell (a self-loop counts twice on the diagonal)11list.get(b).add(new int[]{a, w});12}13}
Live state
n
5
edges
5
Execution trace (dry run table)Show 7 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | n=5 edges=5 | An empty undirected graph on 5 vertices. The three representations below are always updated together: the matrix always has 5 × 5 = 25 cells, the adjacency list grows with the edges, and the edge list is just the edges. |
| 2 | L10 | edge=0-1 matrix cells changed=2 list entries=2 | Add the edge 0-1: both mirror cells [0][1] and [1][0], two list entries and one edge-list entry. |
| 3 | L10 | edge=0-2 matrix cells changed=2 list entries=4 | Add the edge 0-2: both mirror cells [0][2] and [2][0], two list entries and one edge-list entry. |
| 4 | L10 | edge=1-3 matrix cells changed=2 list entries=6 | Add the edge 1-3: both mirror cells [1][3] and [3][1], two list entries and one edge-list entry. |
| 5 | L10 | edge=2-3 matrix cells changed=2 list entries=8 | Add the edge 2-3: both mirror cells [2][3] and [3][2], two list entries and one edge-list entry. |
| 6 | L10 | edge=3-4 matrix cells changed=2 list entries=10 | Add the edge 3-4: both mirror cells [3][4] and [4][3], two list entries and one edge-list entry. |
| 7 | L13 | return=[2, 2, 2, 3, 1] sum of degrees=10 edges=5 | Degrees (row sums) [2, 2, 2, 3, 1]; they add up to 10 = 2 × 5: every edge has two ends (handshaking lemma). |
Edges
An **edge** joins two vertices. In an *undirected* graph the edge {a, b} has no direction: it is the same as {b, a}. In a *directed* graph the edge (a, b) is an arrow from a to b, different from (b, a). A weighted edge also carries a number such as a distance or cost. The number of edges is written m or |E|.
Store an undirected edge once and read it in both directions, or store it twice as two arrows; the representations lesson shows how each structure handles this. A record is a convenient way to carry an edge in Java, with a helper that flips it.
1record Edge(int from, int to, int weight) { // a directed edge; an undirected edge is read both ways2Edge reversed() { return new Edge(to, from, weight); }3boolean isSelfLoop() { return from == to; }4}
One graph, three representations: matrix, adjacency list, edge list
Interactive · edit the inputgrid · 6 × 6
undirected graph · 5 vertices · 0 edges
list[0]
list[1]
list[2]
list[3]
list[4]
edge list (a-b, weight)
An empty undirected graph on 5 vertices. The three representations below are always updated together: the matrix always has 5 × 5 = 25 cells, the adjacency list grows with the edges, and the edge list is just the edges.
1int[][] matrix = new int[n][n]; // adjacency matrix: n * n cells2List<List<int[]>> list = new ArrayList<>(); // adjacency list: {neighbour, weight} per vertex3for (int i = 0; i < n; i++) list.add(new ArrayList<>());4int[][] edgeList = new int[m][]; // edge list: {a, b, weight}5void addEdge(int k, int a, int b, int w, boolean directed) {6edgeList[k] = new int[]{a, b, w};7matrix[a][b]++; // directed: this one cell is the whole edge8list.get(a).add(new int[]{b, w});9if (!directed) {10matrix[b][a]++; // undirected: also the mirror cell (a self-loop counts twice on the diagonal)11list.get(b).add(new int[]{a, w});12}13}
Live state
n
5
edges
5
Execution trace (dry run table)Show 7 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | n=5 edges=5 | An empty undirected graph on 5 vertices. The three representations below are always updated together: the matrix always has 5 × 5 = 25 cells, the adjacency list grows with the edges, and the edge list is just the edges. |
| 2 | L10 | edge=0-1 matrix cells changed=2 list entries=2 | Add the edge 0-1: both mirror cells [0][1] and [1][0], two list entries and one edge-list entry. |
| 3 | L10 | edge=0-2 matrix cells changed=2 list entries=4 | Add the edge 0-2: both mirror cells [0][2] and [2][0], two list entries and one edge-list entry. |
| 4 | L10 | edge=1-3 matrix cells changed=2 list entries=6 | Add the edge 1-3: both mirror cells [1][3] and [3][1], two list entries and one edge-list entry. |
| 5 | L10 | edge=2-3 matrix cells changed=2 list entries=8 | Add the edge 2-3: both mirror cells [2][3] and [3][2], two list entries and one edge-list entry. |
| 6 | L10 | edge=3-4 matrix cells changed=2 list entries=10 | Add the edge 3-4: both mirror cells [3][4] and [4][3], two list entries and one edge-list entry. |
| 7 | L13 | return=[2, 2, 2, 3, 1] sum of degrees=10 edges=5 | Degrees (row sums) [2, 2, 2, 3, 1]; they add up to 10 = 2 × 5: every edge has two ends (handshaking lemma). |
Degree
The **degree** of a vertex is the number of edge ends that touch it. A self-loop has both ends at the same vertex, so it adds 2. In the preset "Undirected, five edges" (0-1 0-2 1-3 2-3 3-4) the degrees are [2, 2, 2, 3, 1]: vertex 3 is touched by three edges and vertex 4 by one. Summing a row of the adjacency matrix gives the degree, which is why the animation's matrix counts the two ends of a loop on the diagonal.
A vertex of degree 0 is isolated and a vertex of degree 1 is a *leaf*. The degrees of all vertices always add up to twice the number of edges (the handshaking lemma, below). Computing them takes one pass over the edge list.
1static int[] degrees(int n, int[][] edges) { // undirected; a self-loop a-a adds 2 to a2int[] deg = new int[n];3for (int[] e : edges) { deg[e[0]]++; deg[e[1]]++; }4return deg;5}
One graph, three representations: matrix, adjacency list, edge list
Interactive · edit the inputgrid · 6 × 6
undirected graph · 5 vertices · 0 edges
list[0]
list[1]
list[2]
list[3]
list[4]
edge list (a-b, weight)
An empty undirected graph on 5 vertices. The three representations below are always updated together: the matrix always has 5 × 5 = 25 cells, the adjacency list grows with the edges, and the edge list is just the edges.
1int[][] matrix = new int[n][n]; // adjacency matrix: n * n cells2List<List<int[]>> list = new ArrayList<>(); // adjacency list: {neighbour, weight} per vertex3for (int i = 0; i < n; i++) list.add(new ArrayList<>());4int[][] edgeList = new int[m][]; // edge list: {a, b, weight}5void addEdge(int k, int a, int b, int w, boolean directed) {6edgeList[k] = new int[]{a, b, w};7matrix[a][b]++; // directed: this one cell is the whole edge8list.get(a).add(new int[]{b, w});9if (!directed) {10matrix[b][a]++; // undirected: also the mirror cell (a self-loop counts twice on the diagonal)11list.get(b).add(new int[]{a, w});12}13}
Live state
n
5
edges
5
Execution trace (dry run table)Show 7 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | n=5 edges=5 | An empty undirected graph on 5 vertices. The three representations below are always updated together: the matrix always has 5 × 5 = 25 cells, the adjacency list grows with the edges, and the edge list is just the edges. |
| 2 | L10 | edge=0-1 matrix cells changed=2 list entries=2 | Add the edge 0-1: both mirror cells [0][1] and [1][0], two list entries and one edge-list entry. |
| 3 | L10 | edge=0-2 matrix cells changed=2 list entries=4 | Add the edge 0-2: both mirror cells [0][2] and [2][0], two list entries and one edge-list entry. |
| 4 | L10 | edge=1-3 matrix cells changed=2 list entries=6 | Add the edge 1-3: both mirror cells [1][3] and [3][1], two list entries and one edge-list entry. |
| 5 | L10 | edge=2-3 matrix cells changed=2 list entries=8 | Add the edge 2-3: both mirror cells [2][3] and [3][2], two list entries and one edge-list entry. |
| 6 | L10 | edge=3-4 matrix cells changed=2 list entries=10 | Add the edge 3-4: both mirror cells [3][4] and [4][3], two list entries and one edge-list entry. |
| 7 | L13 | return=[2, 2, 2, 3, 1] sum of degrees=10 edges=5 | Degrees (row sums) [2, 2, 2, 3, 1]; they add up to 10 = 2 × 5: every edge has two ends (handshaking lemma). |
In-degree/out-degree
In a directed graph each vertex has an **out-degree**, the number of arrows leaving it, and an **in-degree**, the number of arrows entering it. A vertex with in-degree 0 is a *source* and one with out-degree 0 is a *sink*. Every arrow leaves one vertex and enters one vertex, so the out-degrees add up to m and so do the in-degrees.
Switch the animation to "Directed: rows are out-edges": the matrix rows now sum to the out-degrees [2, 1, 1, 1, 0] and the columns to the in-degrees [0, 1, 1, 2, 1]. Both add up to 5, the number of arrows. Vertex 0 is a source (nothing enters it) and vertex 4 is a sink. In-degrees drive topological sorting in the next chapter.
1static int[][] inOutDegrees(int n, int[][] edges) { // returns {in, out}2int[] in = new int[n], out = new int[n];3for (int[] e : edges) { out[e[0]]++; in[e[1]]++; }4return new int[][]{in, out};5}
One graph, three representations: matrix, adjacency list, edge list
Interactive · edit the inputgrid · 6 × 6
undirected graph · 5 vertices · 0 edges
list[0]
list[1]
list[2]
list[3]
list[4]
edge list (a-b, weight)
An empty undirected graph on 5 vertices. The three representations below are always updated together: the matrix always has 5 × 5 = 25 cells, the adjacency list grows with the edges, and the edge list is just the edges.
1int[][] matrix = new int[n][n]; // adjacency matrix: n * n cells2List<List<int[]>> list = new ArrayList<>(); // adjacency list: {neighbour, weight} per vertex3for (int i = 0; i < n; i++) list.add(new ArrayList<>());4int[][] edgeList = new int[m][]; // edge list: {a, b, weight}5void addEdge(int k, int a, int b, int w, boolean directed) {6edgeList[k] = new int[]{a, b, w};7matrix[a][b]++; // directed: this one cell is the whole edge8list.get(a).add(new int[]{b, w});9if (!directed) {10matrix[b][a]++; // undirected: also the mirror cell (a self-loop counts twice on the diagonal)11list.get(b).add(new int[]{a, w});12}13}
Live state
n
5
edges
5
Execution trace (dry run table)Show 7 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | n=5 edges=5 | An empty undirected graph on 5 vertices. The three representations below are always updated together: the matrix always has 5 × 5 = 25 cells, the adjacency list grows with the edges, and the edge list is just the edges. |
| 2 | L10 | edge=0-1 matrix cells changed=2 list entries=2 | Add the edge 0-1: both mirror cells [0][1] and [1][0], two list entries and one edge-list entry. |
| 3 | L10 | edge=0-2 matrix cells changed=2 list entries=4 | Add the edge 0-2: both mirror cells [0][2] and [2][0], two list entries and one edge-list entry. |
| 4 | L10 | edge=1-3 matrix cells changed=2 list entries=6 | Add the edge 1-3: both mirror cells [1][3] and [3][1], two list entries and one edge-list entry. |
| 5 | L10 | edge=2-3 matrix cells changed=2 list entries=8 | Add the edge 2-3: both mirror cells [2][3] and [3][2], two list entries and one edge-list entry. |
| 6 | L10 | edge=3-4 matrix cells changed=2 list entries=10 | Add the edge 3-4: both mirror cells [3][4] and [4][3], two list entries and one edge-list entry. |
| 7 | L13 | return=[2, 2, 2, 3, 1] sum of degrees=10 edges=5 | Degrees (row sums) [2, 2, 2, 3, 1]; they add up to 10 = 2 × 5: every edge has two ends (handshaking lemma). |
Paths
A **path** is a sequence of vertices in which each consecutive pair is joined by an edge and **no vertex repeats**. Its length is the number of edges. Paths are what shortest-path algorithms look for. A single vertex is a path of length 0.
The preset "A path" in the animation checks 0 → 1 → 2 → 3 → 4: every step uses an existing edge, no vertex appears twice, and the verdict is 'path'. Repeating a vertex would turn it into a trail or a walk, as the next items show.
1static boolean isPath(boolean[][] adj, int[] seq) { // simple graph; seq lists the vertices in order2Set<Integer> seen = new HashSet<>();3for (int i = 0; i < seq.length; i++) {4if (!seen.add(seq[i])) return false; // a vertex repeats5if (i > 0 && !adj[seq[i - 1]][seq[i]]) return false; // consecutive vertices must be adjacent6}7return true;8}
Walk, trail, path, closed walk, cycle: classify a vertex sequence
Interactive · edit the inputundirected graph · 5 vertices · 6 edges
#1,5
#2
#3
#4
sequence
The sequence 0 → 1 → 2 → 3 → 0 has 4 steps. Check each step against the edges, then see which vertices and edges repeat.
1String classify(boolean[][] adj, int[] seq) { // simple undirected graph; seq lists the vertices in order2Set<Integer> vertices = new HashSet<>(); Set<Long> edges = new HashSet<>();3boolean repeatedVertex = false, repeatedEdge = false;4vertices.add(seq[0]);5for (int i = 1; i < seq.length; i++) {6int a = seq[i - 1], b = seq[i];7if (!adj[a][b]) return "not a walk"; // consecutive vertices must be joined by an edge8long id = (long) Math.min(a, b) * 1000 + Math.max(a, b);9if (!edges.add(id)) repeatedEdge = true; // the same edge used twice10if (!vertices.add(b) && !(i == seq.length - 1 && b == seq[0])) repeatedVertex = true;11}12boolean closed = seq.length > 1 && seq[0] == seq[seq.length - 1];13if (closed && !repeatedVertex && !repeatedEdge && seq.length - 1 >= 3) return "cycle";14if (closed && !repeatedEdge) return "closed trail";15if (closed) return "closed walk";16if (!repeatedVertex) return "path";17return repeatedEdge ? "walk" : "trail";18}
Live state
sequence
0 → 1 → 2 → 3 → 0
length
4
Execution trace (dry run table)Show 6 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L4 | sequence=0 → 1 → 2 → 3 → 0 length=4 | The sequence 0 → 1 → 2 → 3 → 0 has 4 steps. Check each step against the edges, then see which vertices and edges repeat. |
| 2 | L10 | step=0 → 1 edge used again=false vertex seen before=false | Step 1: 0 → 1 uses the edge 0-1. 1 is new. |
| 3 | L10 | step=1 → 2 edge used again=false vertex seen before=false | Step 2: 1 → 2 uses the edge 1-2. 2 is new. |
| 4 | L10 | step=2 → 3 edge used again=false vertex seen before=false | Step 3: 2 → 3 uses the edge 2-3. 3 is new. |
| 5 | L10 | step=3 → 0 edge used again=false vertex seen before=true | Step 4: 3 → 0 uses the edge 3-0. 0 is the start vertex, so the walk closes. |
| 6 | L17 | return=cycle | 0 → 1 → 2 → 3 → 0 is a cycle. It returns to its start and nothing else repeats. |
Walks
A **walk** is the loosest of the four: consecutive vertices must be joined by an edge, but vertices and edges may repeat as often as you like. Every path, trail and cycle is also a walk. Counting walks of a given length is a classic use of the adjacency matrix: the entry (i, j) of A^k counts the walks of length k from i to j.
In the preset "A walk that repeats an edge" (0 → 1 → 2 → 1 → 3) the animation notices that the edge 1-2 is used twice and that vertex 1 repeats: it is a walk and nothing stricter. The preset "Not a walk: a missing edge" fails at the first step because 0-2 is not an edge.
1static boolean isWalk(boolean[][] adj, int[] seq) { // every consecutive pair must be an edge2for (int i = 1; i < seq.length; i++)3if (!adj[seq[i - 1]][seq[i]]) return false;4return true;5}
Walk, trail, path, closed walk, cycle: classify a vertex sequence
Interactive · edit the inputundirected graph · 5 vertices · 6 edges
#1,5
#2
#3
#4
sequence
The sequence 0 → 1 → 2 → 3 → 0 has 4 steps. Check each step against the edges, then see which vertices and edges repeat.
1String classify(boolean[][] adj, int[] seq) { // simple undirected graph; seq lists the vertices in order2Set<Integer> vertices = new HashSet<>(); Set<Long> edges = new HashSet<>();3boolean repeatedVertex = false, repeatedEdge = false;4vertices.add(seq[0]);5for (int i = 1; i < seq.length; i++) {6int a = seq[i - 1], b = seq[i];7if (!adj[a][b]) return "not a walk"; // consecutive vertices must be joined by an edge8long id = (long) Math.min(a, b) * 1000 + Math.max(a, b);9if (!edges.add(id)) repeatedEdge = true; // the same edge used twice10if (!vertices.add(b) && !(i == seq.length - 1 && b == seq[0])) repeatedVertex = true;11}12boolean closed = seq.length > 1 && seq[0] == seq[seq.length - 1];13if (closed && !repeatedVertex && !repeatedEdge && seq.length - 1 >= 3) return "cycle";14if (closed && !repeatedEdge) return "closed trail";15if (closed) return "closed walk";16if (!repeatedVertex) return "path";17return repeatedEdge ? "walk" : "trail";18}
Live state
sequence
0 → 1 → 2 → 3 → 0
length
4
Execution trace (dry run table)Show 6 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L4 | sequence=0 → 1 → 2 → 3 → 0 length=4 | The sequence 0 → 1 → 2 → 3 → 0 has 4 steps. Check each step against the edges, then see which vertices and edges repeat. |
| 2 | L10 | step=0 → 1 edge used again=false vertex seen before=false | Step 1: 0 → 1 uses the edge 0-1. 1 is new. |
| 3 | L10 | step=1 → 2 edge used again=false vertex seen before=false | Step 2: 1 → 2 uses the edge 1-2. 2 is new. |
| 4 | L10 | step=2 → 3 edge used again=false vertex seen before=false | Step 3: 2 → 3 uses the edge 2-3. 3 is new. |
| 5 | L10 | step=3 → 0 edge used again=false vertex seen before=true | Step 4: 3 → 0 uses the edge 3-0. 0 is the start vertex, so the walk closes. |
| 6 | L17 | return=cycle | 0 → 1 → 2 → 3 → 0 is a cycle. It returns to its start and nothing else repeats. |
Trails
A **trail** is a walk in which **no edge repeats**, although vertices may. Euler's famous question, whether a route can use every edge exactly once, asks for a trail through all edges. A closed trail is called a circuit.
In "A trail that is not a path" the sequence 0 → 1 → 3 → 2 → 1 uses the four different edges 0-1, 1-3, 3-2 and 2-1, so it is a trail, but vertex 1 appears twice, so it is not a path. To check, collect the edges in a set keyed by their two ends in a fixed order, since in an undirected graph a-b and b-a are the same edge.
1static boolean isTrail(boolean[][] adj, int[] seq) { // a walk that never reuses an edge2Set<Long> used = new HashSet<>();3for (int i = 1; i < seq.length; i++) {4int a = seq[i - 1], b = seq[i];5if (!adj[a][b]) return false;6long id = (long) Math.min(a, b) * 1000 + Math.max(a, b); // the same edge in both directions7if (!used.add(id)) return false;8}9return true;10}
Walk, trail, path, closed walk, cycle: classify a vertex sequence
Interactive · edit the inputundirected graph · 5 vertices · 6 edges
#1,5
#2
#3
#4
sequence
The sequence 0 → 1 → 2 → 3 → 0 has 4 steps. Check each step against the edges, then see which vertices and edges repeat.
1String classify(boolean[][] adj, int[] seq) { // simple undirected graph; seq lists the vertices in order2Set<Integer> vertices = new HashSet<>(); Set<Long> edges = new HashSet<>();3boolean repeatedVertex = false, repeatedEdge = false;4vertices.add(seq[0]);5for (int i = 1; i < seq.length; i++) {6int a = seq[i - 1], b = seq[i];7if (!adj[a][b]) return "not a walk"; // consecutive vertices must be joined by an edge8long id = (long) Math.min(a, b) * 1000 + Math.max(a, b);9if (!edges.add(id)) repeatedEdge = true; // the same edge used twice10if (!vertices.add(b) && !(i == seq.length - 1 && b == seq[0])) repeatedVertex = true;11}12boolean closed = seq.length > 1 && seq[0] == seq[seq.length - 1];13if (closed && !repeatedVertex && !repeatedEdge && seq.length - 1 >= 3) return "cycle";14if (closed && !repeatedEdge) return "closed trail";15if (closed) return "closed walk";16if (!repeatedVertex) return "path";17return repeatedEdge ? "walk" : "trail";18}
Live state
sequence
0 → 1 → 2 → 3 → 0
length
4
Execution trace (dry run table)Show 6 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L4 | sequence=0 → 1 → 2 → 3 → 0 length=4 | The sequence 0 → 1 → 2 → 3 → 0 has 4 steps. Check each step against the edges, then see which vertices and edges repeat. |
| 2 | L10 | step=0 → 1 edge used again=false vertex seen before=false | Step 1: 0 → 1 uses the edge 0-1. 1 is new. |
| 3 | L10 | step=1 → 2 edge used again=false vertex seen before=false | Step 2: 1 → 2 uses the edge 1-2. 2 is new. |
| 4 | L10 | step=2 → 3 edge used again=false vertex seen before=false | Step 3: 2 → 3 uses the edge 2-3. 3 is new. |
| 5 | L10 | step=3 → 0 edge used again=false vertex seen before=true | Step 4: 3 → 0 uses the edge 3-0. 0 is the start vertex, so the walk closes. |
| 6 | L17 | return=cycle | 0 → 1 → 2 → 3 → 0 is a cycle. It returns to its start and nothing else repeats. |
Cycles
A **cycle** is a closed path: it starts and ends at the same vertex, the edges are all different, and no other vertex repeats; in a simple graph it has at least 3 edges. A graph with no cycle is *acyclic*; an undirected acyclic graph is a forest. Cycle detection comes up in dependency checking, deadlock detection and spanning trees.
The preset "A cycle" accepts 0 → 1 → 2 → 3 → 0. The classification is a ladder: not a walk, walk, trail, path, closed walk, closed trail, cycle, from loosest to strictest. The code below returns the strictest label that fits.
| Term | Vertices may repeat | Edges may repeat | Closed? |
|---|---|---|---|
| walk | yes | yes | optional |
| trail | yes | no | optional |
| path | no | no | no |
| closed walk / circuit | yes | yes / no | yes |
| cycle | only the start = end | no | yes |
1static String classify(boolean[][] adj, int[] seq) { // simple undirected graph; seq lists the vertices in order2Set<Integer> vertices = new HashSet<>();3Set<Long> edges = new HashSet<>();4boolean repeatedVertex = false, repeatedEdge = false;5vertices.add(seq[0]);6for (int i = 1; i < seq.length; i++) {7int a = seq[i - 1], b = seq[i];8if (!adj[a][b]) return "not a walk";9long id = (long) Math.min(a, b) * 1000 + Math.max(a, b);10if (!edges.add(id)) repeatedEdge = true;11if (!vertices.add(b) && !(i == seq.length - 1 && b == seq[0])) repeatedVertex = true;12}13boolean closed = seq.length > 1 && seq[0] == seq[seq.length - 1];14if (closed && !repeatedVertex && !repeatedEdge && seq.length - 1 >= 3) return "cycle";15if (closed && !repeatedEdge) return "closed trail";16if (closed) return "closed walk";17if (!repeatedVertex) return "path";18return repeatedEdge ? "walk" : "trail";19}
Walk, trail, path, closed walk, cycle: classify a vertex sequence
Interactive · edit the inputundirected graph · 5 vertices · 6 edges
#1,5
#2
#3
#4
sequence
The sequence 0 → 1 → 2 → 3 → 0 has 4 steps. Check each step against the edges, then see which vertices and edges repeat.
1String classify(boolean[][] adj, int[] seq) { // simple undirected graph; seq lists the vertices in order2Set<Integer> vertices = new HashSet<>(); Set<Long> edges = new HashSet<>();3boolean repeatedVertex = false, repeatedEdge = false;4vertices.add(seq[0]);5for (int i = 1; i < seq.length; i++) {6int a = seq[i - 1], b = seq[i];7if (!adj[a][b]) return "not a walk"; // consecutive vertices must be joined by an edge8long id = (long) Math.min(a, b) * 1000 + Math.max(a, b);9if (!edges.add(id)) repeatedEdge = true; // the same edge used twice10if (!vertices.add(b) && !(i == seq.length - 1 && b == seq[0])) repeatedVertex = true;11}12boolean closed = seq.length > 1 && seq[0] == seq[seq.length - 1];13if (closed && !repeatedVertex && !repeatedEdge && seq.length - 1 >= 3) return "cycle";14if (closed && !repeatedEdge) return "closed trail";15if (closed) return "closed walk";16if (!repeatedVertex) return "path";17return repeatedEdge ? "walk" : "trail";18}
Live state
sequence
0 → 1 → 2 → 3 → 0
length
4
Execution trace (dry run table)Show 6 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L4 | sequence=0 → 1 → 2 → 3 → 0 length=4 | The sequence 0 → 1 → 2 → 3 → 0 has 4 steps. Check each step against the edges, then see which vertices and edges repeat. |
| 2 | L10 | step=0 → 1 edge used again=false vertex seen before=false | Step 1: 0 → 1 uses the edge 0-1. 1 is new. |
| 3 | L10 | step=1 → 2 edge used again=false vertex seen before=false | Step 2: 1 → 2 uses the edge 1-2. 2 is new. |
| 4 | L10 | step=2 → 3 edge used again=false vertex seen before=false | Step 3: 2 → 3 uses the edge 2-3. 3 is new. |
| 5 | L10 | step=3 → 0 edge used again=false vertex seen before=true | Step 4: 3 → 0 uses the edge 3-0. 0 is the start vertex, so the walk closes. |
| 6 | L17 | return=cycle | 0 → 1 → 2 → 3 → 0 is a cycle. It returns to its start and nothing else repeats. |
Components
A **connected component** of an undirected graph is a maximal set of vertices in which any two are joined by a path. Every vertex lies in exactly one component; a vertex with no edges is a component of size 1. Components partition the graph, and most algorithms must run once per component to cover everything.
In the preset "Three components" the edges 0-1 1-2 3-4 5-6 6-7 7-5 split 8 vertices into three components of sizes 3, 2 and 3. Finding them takes one search per unlabelled vertex, O(V + E) in total, or a union-find.
1static int countComponents(int n, int[][] edges) {2List<List<Integer>> g = new ArrayList<>();3for (int i = 0; i < n; i++) g.add(new ArrayList<>());4for (int[] e : edges) { g.get(e[0]).add(e[1]); g.get(e[1]).add(e[0]); }5boolean[] seen = new boolean[n];6int count = 0;7for (int s = 0; s < n; s++) {8if (seen[s]) continue;9count++; // a new, unvisited component starts here10Deque<Integer> stack = new ArrayDeque<>();11stack.push(s); seen[s] = true;12while (!stack.isEmpty()) {13int u = stack.pop();14for (int v : g.get(u)) if (!seen[v]) { seen[v] = true; stack.push(v); }15}16}17return count;18}
Components, two-colouring and cycle detection in an undirected graph
Interactive · edit the inputundirected graph · 8 vertices · 6 edges
component of each vertex
Search from an unlabelled vertex: everything the search reaches is one component. Repeat from the next unlabelled vertex until every vertex has a label.
1int[] components(List<List<Integer>> g) { // component id per vertex2int n = g.size(); int[] comp = new int[n]; Arrays.fill(comp, -1);3int id = 0;4for (int s = 0; s < n; s++) {5if (comp[s] != -1) continue;6Deque<Integer> q = new ArrayDeque<>(); q.add(s); comp[s] = id;7while (!q.isEmpty()) {8int u = q.poll();9for (int v : g.get(u)) if (comp[v] == -1) { comp[v] = id; q.add(v); }10}11id++; // the search ended: one whole component labelled12}13return comp;14}15boolean isBipartite(List<List<Integer>> g) {16int n = g.size(); int[] color = new int[n]; Arrays.fill(color, -1);17for (int s = 0; s < n; s++) { // check every component18if (color[s] != -1) continue;19color[s] = 0; Deque<Integer> q = new ArrayDeque<>(); q.add(s);20while (!q.isEmpty()) {21int u = q.poll();22for (int v : g.get(u)) {23if (color[v] == -1) { color[v] = color[u] ^ 1; q.add(v); }24else if (color[v] == color[u]) return false; // an odd cycle25}26}27}28return true;29}30boolean hasCycle(int n, List<int[]>[] g) { // undirected; g[u] holds {v, edgeId}31boolean[] seen = new boolean[n];32for (int s = 0; s < n; s++) if (!seen[s] && dfsCycle(g, s, -1, seen)) return true;33return false;34}35boolean dfsCycle(List<int[]>[] g, int u, int viaEdge, boolean[] seen) {36seen[u] = true;37for (int[] e : g[u]) {38if (e[1] == viaEdge) continue; // do not walk back along the edge we came by39if (seen[e[0]] || dfsCycle(g, e[0], e[1], seen)) return true; // a seen vertex reached by another edge: a cycle40}41return false;42}
Live state
vertices
8
edges
6
Execution trace (dry run table)Show 13 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | vertices=8 edges=6 | Search from an unlabelled vertex: everything the search reaches is one component. Repeat from the next unlabelled vertex until every vertex has a label. |
| 2 | L6 | start=0 component=0 | Vertex 0 is unlabelled: start component C1 here. |
| 3 | L9 | u=0 v=1 component=0 | 0 - 1: 1 is unlabelled, so it belongs to C1 too. |
| 4 | L9 | u=1 v=2 component=0 | 1 - 2: 2 is unlabelled, so it belongs to C1 too. |
| 5 | L11 | components=1 | The queue is empty: component C1 is complete. 1 so far. |
| 6 | L6 | start=3 component=1 | Vertex 3 is unlabelled: start component C2 here. |
| 7 | L9 | u=3 v=4 component=1 | 3 - 4: 4 is unlabelled, so it belongs to C2 too. |
| 8 | L11 | components=2 | The queue is empty: component C2 is complete. 2 so far. |
| 9 | L6 | start=5 component=2 | Vertex 5 is unlabelled: start component C3 here. |
| 10 | L9 | u=5 v=6 component=2 | 5 - 6: 6 is unlabelled, so it belongs to C3 too. |
| 11 | L9 | u=5 v=7 component=2 | 5 - 7: 7 is unlabelled, so it belongs to C3 too. |
| 12 | L11 | components=3 | The queue is empty: component C3 is complete. 3 so far. |
| 13 | L13 | return=3 sizes=[3, 2, 3] | 3 components with sizes [3, 2, 3]. A vertex with no edge is a component of size 1. Cost O(V + E). |
Connectedness
An undirected graph is **connected** when it has exactly one component: a path exists between every pair of vertices. A directed graph needs two notions. It is **weakly connected** if it is connected after ignoring the direction of arrows, and **strongly connected** if every vertex can reach every other following the arrows. Strong connectivity is stronger: a directed path 0 → 1 → 2 is weakly connected but not strongly.
To test strong connectivity, run one search from vertex 0 along the arrows and another along the reversed arrows: the graph is strongly connected exactly when both reach every vertex (every vertex is reachable from 0, and 0 is reachable from every vertex). Chapter 38 finds the strongly connected components themselves.
1static boolean isConnected(int n, int[][] edges) { // undirected2int[] parent = new int[n];3for (int i = 0; i < n; i++) parent[i] = i;4int comps = n;5for (int[] e : edges) {6int a = e[0], b = e[1];7while (parent[a] != a) a = parent[a];8while (parent[b] != b) b = parent[b];9if (a != b) { parent[a] = b; comps--; }10}11return comps == 1;12}13static boolean isStronglyConnected(int n, int[][] arrows) { // directed14return reachesAll(n, arrows, false) && reachesAll(n, arrows, true);15}16private static boolean reachesAll(int n, int[][] arrows, boolean reversed) { // from vertex 017List<List<Integer>> g = new ArrayList<>();18for (int i = 0; i < n; i++) g.add(new ArrayList<>());19for (int[] e : arrows) { if (reversed) g.get(e[1]).add(e[0]); else g.get(e[0]).add(e[1]); }20boolean[] seen = new boolean[n];21Deque<Integer> q = new ArrayDeque<>();22q.add(0); seen[0] = true; int count = 1;23while (!q.isEmpty()) {24int u = q.poll();25for (int v : g.get(u)) if (!seen[v]) { seen[v] = true; count++; q.add(v); }26}27return count == n;28}
Components, two-colouring and cycle detection in an undirected graph
Interactive · edit the inputundirected graph · 8 vertices · 6 edges
component of each vertex
Search from an unlabelled vertex: everything the search reaches is one component. Repeat from the next unlabelled vertex until every vertex has a label.
1int[] components(List<List<Integer>> g) { // component id per vertex2int n = g.size(); int[] comp = new int[n]; Arrays.fill(comp, -1);3int id = 0;4for (int s = 0; s < n; s++) {5if (comp[s] != -1) continue;6Deque<Integer> q = new ArrayDeque<>(); q.add(s); comp[s] = id;7while (!q.isEmpty()) {8int u = q.poll();9for (int v : g.get(u)) if (comp[v] == -1) { comp[v] = id; q.add(v); }10}11id++; // the search ended: one whole component labelled12}13return comp;14}15boolean isBipartite(List<List<Integer>> g) {16int n = g.size(); int[] color = new int[n]; Arrays.fill(color, -1);17for (int s = 0; s < n; s++) { // check every component18if (color[s] != -1) continue;19color[s] = 0; Deque<Integer> q = new ArrayDeque<>(); q.add(s);20while (!q.isEmpty()) {21int u = q.poll();22for (int v : g.get(u)) {23if (color[v] == -1) { color[v] = color[u] ^ 1; q.add(v); }24else if (color[v] == color[u]) return false; // an odd cycle25}26}27}28return true;29}30boolean hasCycle(int n, List<int[]>[] g) { // undirected; g[u] holds {v, edgeId}31boolean[] seen = new boolean[n];32for (int s = 0; s < n; s++) if (!seen[s] && dfsCycle(g, s, -1, seen)) return true;33return false;34}35boolean dfsCycle(List<int[]>[] g, int u, int viaEdge, boolean[] seen) {36seen[u] = true;37for (int[] e : g[u]) {38if (e[1] == viaEdge) continue; // do not walk back along the edge we came by39if (seen[e[0]] || dfsCycle(g, e[0], e[1], seen)) return true; // a seen vertex reached by another edge: a cycle40}41return false;42}
Live state
vertices
8
edges
6
Execution trace (dry run table)Show 13 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | vertices=8 edges=6 | Search from an unlabelled vertex: everything the search reaches is one component. Repeat from the next unlabelled vertex until every vertex has a label. |
| 2 | L6 | start=0 component=0 | Vertex 0 is unlabelled: start component C1 here. |
| 3 | L9 | u=0 v=1 component=0 | 0 - 1: 1 is unlabelled, so it belongs to C1 too. |
| 4 | L9 | u=1 v=2 component=0 | 1 - 2: 2 is unlabelled, so it belongs to C1 too. |
| 5 | L11 | components=1 | The queue is empty: component C1 is complete. 1 so far. |
| 6 | L6 | start=3 component=1 | Vertex 3 is unlabelled: start component C2 here. |
| 7 | L9 | u=3 v=4 component=1 | 3 - 4: 4 is unlabelled, so it belongs to C2 too. |
| 8 | L11 | components=2 | The queue is empty: component C2 is complete. 2 so far. |
| 9 | L6 | start=5 component=2 | Vertex 5 is unlabelled: start component C3 here. |
| 10 | L9 | u=5 v=6 component=2 | 5 - 6: 6 is unlabelled, so it belongs to C3 too. |
| 11 | L9 | u=5 v=7 component=2 | 5 - 7: 7 is unlabelled, so it belongs to C3 too. |
| 12 | L11 | components=3 | The queue is empty: component C3 is complete. 3 so far. |
| 13 | L13 | return=3 sizes=[3, 2, 3] | 3 components with sizes [3, 2, 3]. A vertex with no edge is a component of size 1. Cost O(V + E). |
Practice ladder
Now try it yourself
Easy
- Find the Town Judge
- Find Center of Star Graph
Medium
- Find if Path Exists in Graph
- Number of Connected Components in an Undirected Graph
Advanced
- Reconstruct Itinerary
- Minimum Number of Vertices to Reach All Nodes
Key takeaways
- A graph is vertices plus edges; label the vertices 0 to n - 1 in code.
- Degree counts edge ends (a self-loop counts twice); directed graphs split it into in-degree and out-degree.
- Walk ⊇ trail ⊇ path, and a cycle is a closed path; components partition the vertices.