Back to Graph Theory Series

Matrix-Tree Theorem

October 11, 2026 Wasil Zafar 28 min read

A determinant of a graph Laplacian counts its spanning trees exactly, linking nineteenth-century electrical networks to modern spectral graph theory.

Contents

  1. From Circuits to Counting
  2. Spanning Trees
  3. Build the Laplacian
  4. The Theorem
  5. Worked Example
  6. Why It Works
  7. Variants
  8. Implementation
  9. Applications
  10. Pitfalls
  11. Complexity and Choice

From Electrical Circuits to Combinatorial Counting

Kirchhoff introduced the theorem while studying electrical networks: currents entering a junction must balance currents leaving it. That local conservation law is encoded by the graph Laplacian. The surprise is that the same matrix also knows how many global, cycle-free backbones connect every vertex.

IntuitionA connected network may contain many redundant routes. A spanning tree is one way to keep just enough roads for every location to remain reachable—no cycles, no extra edge. The Matrix–Tree Theorem counts every such minimal backbone at once, without generating them individually.

Why the result matters

1847Kirchhoff connects circuit equations, determinants, and tree-like network structures.
LaterLaplacians become central to spectral graph theory, random walks, and electrical resistance.
TodayTree counts measure redundancy, normalize random-tree distributions, and support graph algorithms.

What Exactly Is Being Counted?

For an undirected graph $G=(V,E)$, a spanning tree is a subset of edges that:

Spans

It contains every vertex of $G$.

Connects

Every pair of vertices remains linked by a path.

Has no cycle

For $n$ vertices, it uses exactly $n-1$ edges.

Deleting any edge from a spanning tree disconnects it; adding any unused edge creates exactly one cycle. The count of spanning trees is denoted $\tau(G)$.

A square-with-diagonal graph and one of its spanning treesThe full graph has four vertices, four cycle edges, and a diagonal from vertex one to three. A sample spanning tree keeps edges one-two, two-three, and three-four while fading the unused edges. Original graph: 5 edgesOne spanning tree: 3 edges 1234 1234 cycle 1–2–3–4–1 plus diagonal 1–3connected + acyclic + all four vertices
The full graph has eight different spanning trees. The right panel shows one: $1-2-3-4$.
Graph familySpanning-tree countQuick reason
A tree$1$Its only connected backbone is itself.
Cycle $C_n$$n$Delete any one cycle edge.
Complete graph $K_n$$n^{n-2}$Cayley’s formula.
Disconnected graph$0$No tree can span all components.

Build the Laplacian from Local Information

For a simple undirected graph, let $A$ be the adjacency matrix and $D$ the diagonal degree matrix. The Laplacian is:

$$L=D-A$$
EntryValueInterpretation
$L_{ii}$$\deg(i)$How many edges touch vertex $i$.
$L_{ij}$, $i\ne j$$-1$ if $i$ and $j$ are adjacent, otherwise $0$Which pairs are directly coupled.

Every row sums to zero: degree on the diagonal is canceled by one $-1$ for each neighbor. Consequently $L\mathbf{1}=0$, so $L$ is singular and $\det(L)=0$. The zero determinant is not a failure—it reflects the fact that absolute electrical potential can be shifted by a constant without changing any voltage difference.

Why delete a row and column?

Removing the same row and column fixes one vertex as a reference, eliminates the unavoidable all-ones null direction, and leaves a cofactor whose determinant contains the tree count.

Kirchhoff’s Matrix–Tree Theorem

Choose any vertex $r$ and delete row $r$ and column $r$ from $L$. Call the resulting $(n-1)\times(n-1)$ principal minor $L^{(r)}$. Then:

$$\boxed{\tau(G)=\det\!\left(L^{(r)}\right)}$$

The answer does not depend on which vertex is deleted. If $G$ is disconnected, every such determinant is zero; if $G$ is connected, each is the same positive integer.

From Graph to Exact Count
flowchart TD
    G[Graph edges] --> L[Build Laplacian L = D − A]
    L --> S[Delete one matching row and column]
    S --> D[Compute the cofactor determinant]
    D --> T[Result is the spanning-tree count]

Worked Example: A Square with One Diagonal

Use the graph from the first figure, with edges $\{12,23,34,41,13\}$. Its vertex degrees are $(3,2,3,2)$, so:

$$L=\begin{bmatrix} 3&-1&-1&-1\\ -1&2&-1&0\\ -1&-1&3&-1\\ -1&0&-1&2 \end{bmatrix}$$

Delete row $4$ and column $4$. The cofactor is:

$$L^{(4)}=\begin{bmatrix}3&-1&-1\\-1&2&-1\\-1&-1&3\end{bmatrix}$$
Deleting a Laplacian row and column to obtain a cofactorThe four by four Laplacian of the square-with-diagonal graph is shown with row four and column four marked for deletion. The remaining three by three cofactor has determinant eight. Laplacian LCofactor L⁽⁴⁾ rowcolumn 3−1−1−1 −12−10 −1−13−1 −10−12 delete row 4and column 4 3−1−1 −12−1 −1−13 det(L⁽⁴⁾) = 8 red cells are removedthere are exactly eight spanning trees
Any matching row and column could be deleted. Removing vertex 4 leaves a $3\times3$ minor with determinant $8$.

Expanding the determinant gives $15-4-3=8$. The theorem has counted all eight backbones without listing any of them.

Predict before calculating

Remove the diagonal edge $1-3$. What should the determinant become?

Answer: $4$. The graph becomes the cycle $C_4$, and deleting any one of its four edges gives a spanning tree.

Why a Determinant Counts Trees

The cleanest proof intuition uses an oriented incidence matrix. Give every undirected edge an arbitrary direction and create a matrix $B$ with one column per edge: the column has $+1$ at one endpoint, $-1$ at the other, and zero elsewhere. The chosen orientations are only bookkeeping.

$$L=BB^\mathsf{T}$$

Delete the row of a reference vertex $r$ from $B$ to obtain $B_r$. Then $L^{(r)}=B_rB_r^\mathsf{T}$. Cauchy–Binet expands the determinant as a sum over every subset $S$ of exactly $n-1$ edge columns:

$$\det\!\left(B_rB_r^\mathsf{T}\right)= \sum_{\substack{S\subseteq E\\|S|=n-1}}\det(B_{r,S})^2$$

If $S$ is a spanning tree

The reduced incidence columns are independent and $\det(B_{r,S})=\pm1$. Squaring contributes exactly $1$.

If $S$ is not a spanning tree

A cycle or disconnection makes the columns dependent, so the determinant is $0$.

The determinant is therefore a filter: it examines every $(n-1)$-edge subset algebraically, gives one vote to each spanning tree, and zero to everything else. Squaring removes the arbitrary incidence orientation signs.

Spectral form

If a connected graph has Laplacian eigenvalues $0=\lambda_1<\lambda_2\le\cdots\le\lambda_n$, then $\tau(G)=\frac{1}{n}\prod_{i=2}^{n}\lambda_i$. The same count is encoded by every cofactor and by the nonzero spectrum.

Weighted, Parallel, and Directed Variants

The determinant framework adapts naturally, but the meaning of each entry must match the graph model.

Graph modelLaplacian constructionWhat the cofactor gives
Weighted undirected$L_{ii}$ is incident weight sum; $L_{ij}$ is minus the total weight between $i$ and $j$$\sum_T\prod_{e\in T}w_e$
Undirected multigraphParallel-edge multiplicities add in degrees and off-diagonalsEach distinct parallel-edge choice is counted.
Directed, $D_{\text{out}}-A$ conventionRows encode outgoing weightsThe root cofactor counts in-arborescences directed toward that root.
Directed, transposed/in-degree conventionReverse the Laplacian orientation consistentlyThe corresponding cofactor counts out-arborescences from the root.

What a weighted count means

It is not usually the number of trees. Each tree contributes the product of its edge weights. Setting every weight to $1$ recovers the ordinary count.

Self-loops never belong to a spanning tree. The safest implementation simply ignores them while building the Laplacian. For directed graphs, always state the adjacency orientation and degree convention; transposing a definition swaps which arborescence orientation is counted.

Implementation with Exact Arithmetic

For integer or integer-weighted graphs, the answer is an integer and may be enormous. Fraction-free Bareiss elimination keeps every intermediate value integral and avoids the rounding risk of a floating-point determinant.

def bareiss(matrix):
    n = len(matrix)
    if n == 0:
        return 1
    a = [row[:] for row in matrix]
    previous = 1
    sign = 1

    for k in range(n - 1):
        if a[k][k] == 0:
            pivot_row = next((r for r in range(k + 1, n)
                              if a[r][k] != 0), None)
            if pivot_row is None:
                return 0
            a[k], a[pivot_row] = a[pivot_row], a[k]
            sign = -sign

        pivot = a[k][k]
        for i in range(k + 1, n):
            for j in range(k + 1, n):
                a[i][j] = (a[i][j] * pivot
                           - a[i][k] * a[k][j]) // previous
            a[i][k] = 0
        previous = pivot
    return sign * a[-1][-1]

def spanning_tree_count(n, edges):
    if n == 0:
        return 0
    laplacian = [[0] * n for _ in range(n)]
    for u, v, weight in edges:
        if u == v:
            continue
        laplacian[u][u] += weight
        laplacian[v][v] += weight
        laplacian[u][v] -= weight
        laplacian[v][u] -= weight
    minor = [row[:-1] for row in laplacian[:-1]]
    return bareiss(minor)

edges = [(0,1,1), (1,2,1), (2,3,1), (3,0,1), (0,2,1)]
print(spanning_tree_count(4, edges))  # 8
#include <boost/multiprecision/cpp_int.hpp>
#include <utility>
#include <vector>
using boost::multiprecision::cpp_int;
using Matrix = std::vector<std::vector<cpp_int>>;

cpp_int bareiss(Matrix a) {
    int n = static_cast<int>(a.size());
    if (n == 0) return 1;
    cpp_int previous = 1, sign = 1;

    for (int k = 0; k + 1 < n; ++k) {
        int row = k;
        while (row < n && a[row][k] == 0) ++row;
        if (row == n) return 0;
        if (row != k) {
            std::swap(a[row], a[k]);
            sign = -sign;
        }
        cpp_int pivot = a[k][k];
        for (int i = k + 1; i < n; ++i) {
            for (int j = k + 1; j < n; ++j)
                a[i][j] = (a[i][j] * pivot
                         - a[i][k] * a[k][j]) / previous;
            a[i][k] = 0;
        }
        previous = pivot;
    }
    return sign * a[n - 1][n - 1];
}
import java.math.BigInteger;

public class MatrixTree {
static BigInteger bareiss(BigInteger[][] input) {
    int n = input.length;
    if (n == 0) return BigInteger.ONE;
    BigInteger[][] a = new BigInteger[n][];
    for (int i = 0; i < n; i++) a[i] = input[i].clone();
    BigInteger previous = BigInteger.ONE;
    int sign = 1;

    for (int k = 0; k + 1 < n; k++) {
        int row = k;
        while (row < n && a[row][k].signum() == 0) row++;
        if (row == n) return BigInteger.ZERO;
        if (row != k) {
            BigInteger[] temp = a[row]; a[row] = a[k]; a[k] = temp;
            sign = -sign;
        }
        BigInteger pivot = a[k][k];
        for (int i = k + 1; i < n; i++) {
            for (int j = k + 1; j < n; j++) {
                a[i][j] = a[i][j].multiply(pivot)
                    .subtract(a[i][k].multiply(a[k][j]))
                    .divide(previous);
            }
            a[i][k] = BigInteger.ZERO;
        }
        previous = pivot;
    }
    BigInteger result = a[n - 1][n - 1];
    return sign > 0 ? result : result.negate();
}
}

Implementation checklist

  • Ignore self-loops and accumulate parallel-edge weights.
  • Update both diagonal endpoints and both symmetric off-diagonal entries.
  • Delete one row and the same column.
  • Use exact or modular arithmetic when the answer must be exact.
  • Handle $n=1$: the empty $0\times0$ minor has determinant $1$.
  • Return zero naturally for a disconnected graph.

Why Count Spanning Trees?

Reliability

Structural Redundancy

More spanning trees mean more distinct minimal connected backbones, a useful connectivity indicator for infrastructure networks.

Randomized Algorithms

Tree Distributions

The weighted determinant is the normalizing constant for weighted random spanning trees.

Electrical Networks

Effective Resistance

For conductance weight $w_e$, the probability that edge $e$ appears in a weighted random spanning tree is $w_eR_{\mathrm{eff}}(e)$.

Graph Descriptors

Molecular Structure

Spanning-tree counts and related indices summarize connectivity patterns in molecular graphs.

Laplacian cofactors also appear in network synchronization, determinant-based probabilistic models, and analyses of graph sparsification. The theorem is valuable because it turns an exponentially large family of trees into one polynomial-size determinant.

Pitfalls That Produce the Wrong Count

MistakeSymptomFix
Taking $\det(L)$Always gets zeroTake an $(n-1)\times(n-1)$ cofactor.
Deleting different row/column indices without tracking cofactor signConfusing sign or conventionUse a principal minor: delete the same index.
Using floating-point determinant and roundingOff-by-one errors for large or ill-conditioned matricesUse Bareiss, big integers, or modular arithmetic.
Ignoring parallel-edge multiplicityUndercounts multigraph treesAccumulate multiplicity or total weight.
Applying the undirected Laplacian to directed edgesCounts the wrong objectUse a documented directed Matrix–Tree convention.
Expecting the trees themselvesOnly a number is returnedUse enumeration or sampling algorithms when witnesses are needed.

Complexity, Scale, and Algorithm Choice

Building a dense Laplacian costs $O(n^2+m)$ storage work, while dense determinant elimination uses $O(n^3)$ arithmetic operations and $O(n^2)$ memory. Exact integers can grow to many bits, so bit complexity may dominate even when the operation count looks modest.

GoalGood starting pointWhy
Construct one minimum spanning treeKruskal or PrimOptimization is different from counting.
Exact count, moderate graphCofactor + BareissSimple, deterministic exact arithmetic.
Exact count modulo a primeModular Gaussian eliminationControls integer growth.
Huge exact integer countSeveral modular determinants + CRTReconstructs the answer from bounded residues.
Sample a uniform random treeWilson’s algorithmProduces a tree directly without computing the count first.
List every spanning treeEnumeration algorithmOutput itself may be exponential.

Mental model to keep

The Laplacian records local degree and adjacency balance. Removing one reference vertex makes that balance nonsingular, and the resulting determinant gives one unit for every spanning tree and zero for every invalid edge subset.