Note
This article is translated from e-maxx Prüfer Code. The original numbers vertices from , whereas this article uses the numbering convention more common in practice, starting from .
This article introduces the Prüfer sequence (Prüfer code), a method of representing a labeled tree by a unique integer sequence.
The Prüfer sequence can be used to prove Cayley's formula. We will also explain how to count the ways to add edges to a graph so that it becomes connected.
Note: We do not consider trees with vertex.
Prüfer Sequence
Introduction
A Prüfer sequence represents a tree with labeled vertices by integers from , specifically integers. It can also be understood as a bijection between the spanning trees of a complete graph and sequences, which is useful in common combinatorial counting problems.
Heinz Prüfer invented this sequence in 1918 to prove Cayley's formula.
Constructing a Prüfer Sequence from a Tree
The Prüfer sequence is constructed as follows: each time, choose and delete the leaf with the smallest label, and record the vertex to which it was connected in the sequence. After repeating this times, only two vertices remain and the algorithm ends.
Clearly, using a heap gives complexity .
Implementation
C++
// Code excerpted from the original; vertices are numbered from 0
vector<vector<int>> adj;
vector<int> pruefer_code() {
int n = adj.size();
set<int> leafs;
vector<int> degree(n);
vector<bool> killed(n);
for (int i = 0; i < n; i++) {
degree[i] = adj[i].size();
if (degree[i] == 1) leafs.insert(i);
}
vector<int> code(n - 2);
for (int i = 0; i < n - 2; i++) {
int leaf = *leafs.begin();
leafs.erase(leafs.begin());
killed[leaf] = true;
int v;
for (int u : adj[leaf])
if (!killed[u]) v = u;
code[i] = v;
if (--degree[v] == 1) leafs.insert(v);
}
return code;
}
Python
# Vertices are numbered from 0
adj = [[]]
def pruefer_code():
n = len(adj)
leafs = set()
degree = [0] * n
killed = [False] * n
for i in range(1, n):
degree[i] = len(adj[i])
if degree[i] == 1:
leafs.intersection(i)
code = [0] * (n - 2)
for i in range(1, n - 2):
leaf = leafs[0]
leafs.pop()
killed[leaf] = True
for u in adj[leaf]:
if killed[u] == False:
v = u
code[i] = v
if degree[v] == 1:
degree[v] = degree[v] - 1
leafs.intersection(v)
return code
For example, this is the construction process for the Prüfer sequence of a tree with 7 vertices:

The resulting sequence is .
There is also a linear-time construction algorithm.
Linear-Time Construction of a Prüfer Sequence
The essence of the linear construction is to maintain a pointer to the vertex we are about to delete. First observe that the number of leaves is non-increasing: deleting a leaf either leaves the total number of leaves unchanged or decreases it by 1.
We therefore consider the following process. Maintain a pointer . Initially, points to the leaf with the smallest label. At the same time, maintain the degree of every vertex so that we can determine whether deleting a vertex creates a new leaf. The operations are:
- Delete the vertex pointed to by , and check whether a new leaf is created.
- If a new leaf is created, suppose its label is and compare . If , do nothing else; otherwise, immediately delete , check whether deleting creates another new leaf, and repeat step until no new vertex is created or the new vertex has label .
- Increment the pointer until an undeleted leaf is encountered;
Correctness
Repeating the above operations times completes the construction of the sequence. We now consider the correctness of the algorithm.
is the leaf with the smallest current label. If deleting creates no leaf, we can only search for the next leaf; if it creates a leaf :
- If , scanning forward from will reach it anyway, so do nothing;
- If $x> adj; vector<int> parent;
void dfs(int v) { for (int u : adj[v]) { if (u != parent[v]) parent[u] = v, dfs(u); } }
vector<int> pruefer_code() { int n = adj.size(); parent.resize(n), parent[n - 1] = -1; dfs(n - 1);
int ptr = -1; vector<int> degree(n); for (int i = 0; i < n; i++) { degree[i] = adj[i].size(); if (degree[i] == 1 && ptr == -1) ptr = i; }
vector<int> code(n - 2); int leaf = ptr; for (int i = 0; i < n - 2; i++) { int next = parent[leaf]; code[i] = next; if (--degree[next] == 1 && next < ptr) { leaf = next; } else { ptr++; while (degree[ptr] != 1) ptr++; leaf = ptr; } } return code; }
**Python**
```python
# Likewise, starting from 0
adj = [[]]
parent = [0] * n
def dfs(v):
for u in adj[v]:
if u != parent[v]:
parent[u] = v
dfs(u)
def pruefer_code():
n = len(adj)
parent[n - 1] = -1
dfs(n - 1)
ptr = -1
degree = [0] * n
for i in range(0, n):
degree[i] = len(adj[i])
if degree[i] == 1 and ptr == -1:
ptr = i
code = [0] * (n - 2)
leaf = ptr
for i in range(0, n - 2):
next = parent[leaf]
code[i] = next
if degree[next] == 1 and next < ptr:
degree[next] = degree[next] - 1
leaf = next
else:
ptr = ptr + 1
while degree[ptr] != 1:
ptr = ptr + 1
leaf = ptr
return code
Properties of the Prüfer Sequence
- After constructing the Prüfer sequence, two vertices remain in the original tree, one of which is necessarily the vertex with the largest label, .
- Each vertex appears in the sequence a number of times equal to its degree minus . (Vertices that do not appear are leaves.)
Reconstructing a Tree from a Prüfer Sequence
The reconstruction method is similar. From the properties of the Prüfer sequence, we can obtain the degree of every vertex in the original tree. We can then find the leaf with the smallest label; this leaf must be connected to the vertex corresponding to the first number in the Prüfer sequence. We then decrease the degrees of these two vertices by one.
By this point you may already know how to proceed. Each time, choose the smallest-labeled vertex of degree , connect it to the vertex currently selected from the Prüfer sequence, and decrease the degrees of both vertices. At the end, two vertices of degree remain, one of which is vertex ; connect them. Maintaining this process with a heap, and adding a vertex to the heap whenever its degree decreases to , gives complexity .
Implementation
// Code excerpted from the original
vector<pair<int, int>> pruefer_decode(vector<int> const& code) {
int n = code.size() + 2;
vector<int> degree(n, 1);
for (int i : code) degree[i]++;
set<int> leaves;
for (int i = 0; i < n; i++)
if (degree[i] == 1) leaves.insert(i);
vector<pair<int, int>> edges;
for (int v : code) {
int leaf = *leaves.begin();
leaves.erase(leaves.begin());
edges.emplace_back(leaf, v);
if (--degree[v] == 1) leaves.insert(v);
}
edges.emplace_back(*leaves.begin(), n - 1);
return edges;
}
Linear-Time Tree Reconstruction
Use the same method as for the linear construction of a Prüfer sequence. When deleting degree, a new leaf may be created; compare this leaf with the pointer and process it first if its label is smaller.
Implementation
// Code excerpted from the original
vector<pair<int, int>> pruefer_decode(vector<int> const& code) {
int n = code.size() + 2;
vector<int> degree(n, 1);
for (int i : code) degree[i]++;
int ptr = 0;
while (degree[ptr] != 1) ptr++;
int leaf = ptr;
vector<pair<int, int>> edges;
for (int v : code) {
edges.emplace_back(leaf, v);
if (--degree[v] == 1 && v < ptr) {
leaf = v;
} else {
ptr++;
while (degree[ptr] != 1) ptr++;
leaf = ptr;
}
}
edges.emplace_back(leaf, n - 1);
return edges;
}
These processes make it clear that the Prüfer sequence establishes a bijection between labeled unrooted trees and sequences.
Cayley Formula (Cayley's formula)
The complete graph has spanning trees.
How can we prove this? There are many methods, but a Prüfer-sequence proof is very simple. Every integer sequence of length with value range corresponds bijectively to a spanning tree through the Prüfer sequence, so the number of possibilities is .
Number of Ways to Make a Graph Connected
The Prüfer sequence may be more powerful than you expect. It yields formulas more general than Cayley's formula. Consider the following problem:
An undirected labeled graph with vertices, edges, and connected components is given. We want to add edges so that the entire graph becomes connected. Count the number of ways.
Proof
Let denote the number of vertices in the -th connected component. We consider constructing a Prüfer sequence on the connected components. Since there are many ways to connect two components, this is not an ordinary Prüfer sequence. Let be the degree of the -th connected component. Since the sum of the degrees is twice the number of edges, . For a fixed sequence , the number of ways to construct the Prüfer sequence is
For the -th connected component, there are ways to choose its connections. Therefore, for a fixed sequence , the number of ways to make the graph connected is
We now enumerate the sequences , giving
This is not a particularly pleasant expression. But do not panic! We have the multinomial theorem:
Now make the substitution in the original expression. Clearly, , so it becomes
Simplifying gives
That is
which is the answer.