DSA DSA In Depth
0%
lesson matroidsgreedycombinatorial-optimization

Matroid Theory: Graphic & Linear Matroids and When the Greedy Algorithm Is Guaranteed Optimal

7 min read Module 6.1: Recursion, Backtracking, Pruning & Greedy Exchange Arguments Updated Oct 23, 2026
📄 Cheat Sheet
Matroids & Why Greedy Algorithms Work (Hereditary Independence & Exchange Property)
IIT Madras B.S. Degree Programme Matroids & Why Greedy Algorithms Work (Hereditary Independence & Exchange Property) Watch on YouTube ↗

Explains the axiomatic structure of matroids, the Rado-Edmonds Theorem, and why Kruskal's MST and Gaussian basis selection are the exact same greedy algorithm.

0:00 / --:--

1. The Three Matroid Axioms & The Rado-Edmonds Theorem

A subset system $M = (E, \mathcal{I})$, where $E$ is a finite ground set and $\mathcal{I} \subseteq 2^E$ is a family of independent sets, is called a Matroid (Whitney, 1935) if it satisfies three axioms:

  1. Non-Emptiness: $\emptyset \in \mathcal{I}$.
  2. Hereditary Property (Downward Closure): If $B \in \mathcal{I}$ and $A \subseteq B$, then $A \in \mathcal{I}$.
  3. Exchange Property (Augmentation): If $A, B \in \mathcal{I}$ and $|A| < |B|$, then there exists some element $x \in B \setminus A$ such that $A \cup \{x\} \in \mathcal{I}$. (Consequently, all maximal independent sets—called bases—have the exact same cardinality $\text{rank}(E)$!)

Core Invariant — The Rado–Edmonds Theorem (1957 / 1971)

Let $(E, \mathcal{I})$ be any hereditary system ($\emptyset \in \mathcal{I}$ and downward closed). The Best-In Greedy Algorithm (sort $E$ by weight $w(e_1) \ge w(e_2) \ge \dots \ge w(e_n)$, initialize $S = \emptyset$, and for each $e_i$ with $w(e_i) > 0$, set $S \leftarrow S \cup \{e_i\}$ if $S \cup \{e_i\} \in \mathcal{I}$) returns a maximum-weight independent set for every weight function $w: E \to \mathbb{R}_{\ge 0}$ if and only if $(E, \mathcal{I})$ is a matroid.

2. Canonical Matroids in Systems & Competitive Programming

  • Graphic Matroid $M(G)$ (Kruskal's MST): $E$ is the edge set of an undirected graph $G$, and $I \in \mathcal{I}$ iff $I$ is a forest (contains no cycles). Independence testing $S \cup \{e\} \in \mathcal{I}$ takes $O(\alpha(V))$ time using Disjoint Set Union (DSU).
  • Linear / Vector Matroid over $\mathbb{F}_2$ (XOR Basis): $E$ is a set of $d$-bit integers (vectors in $\mathbb{F}_2^d$), and $I \in \mathcal{I}$ iff the vectors in $I$ are linearly independent under bitwise XOR ($\oplus$). Maintaining an upper-triangular XOR Basis of size $\le d$ tests independence and inserts a new $64$-bit vector in $O(d)$ bitwise operations!
  • Transversal / Unit-Time Deadline Scheduling Matroid: Jobs with unit processing time $t_i = 1$, deadlines $d_i \in \{1..n\}$, and profits $w_i > 0$. A subset of jobs $S$ is independent iff they can all be scheduled before their deadlines (checked in $O(\alpha(n))$ time by DSU: assign each job to the latest free slot $\le d_i$).
  • Why Bipartite Matching Is NOT a Matroid: In a 4-vertex path $u_1 - v_1 - u_2 - v_2$, $A = \{(v_1, u_2)\}$ (size 1) and $B = \{(u_1, v_1), (u_2, v_2)\}$ (size 2) are both valid matchings, yet neither edge in $B$ can be added to $A$ without sharing a vertex! Instead, Bipartite Matching is the intersection of two Partition Matroids ($M_1$ restricts left-degrees $\le 1$, $M_2$ restricts right-degrees $\le 1$). Optimizing over the intersection of 2 matroids is solvable in polynomial time (Augmenting Paths / Matroid Intersection), whereas the intersection of $\ge 3$ matroids is NP-hard!
flowchart TD Sort["1. Sort ground set E descending by weight:
w(e_1) >= w(e_2) >= ... >= w(e_n)"] --> Loop["2. For each element e_i with w(e_i) > 0"] Loop --> Oracle{"3. Independence Oracle:
Is S U {e_i} in I?"} Oracle -- "Yes (Graphic: DSU find(u) != find(v)
Linear: XOR basis reduction != 0)" --> Add["S = S U {e_i}"] Oracle -- "No (Dependent / Circuit)" --> Skip["Discard e_i"] Add --> Next["Next element -> Optimal Basis S*"] Skip --> Next

Hardware Reality — 64-Bit $\mathbb{F}_2$ XOR Basis Fits in 8 Cache Lines

A linear matroid basis over 64-bit integers (uint64_t basis[64]) occupies exactly $64 \times 8 = 512$ bytes (8 L1 cache lines). Testing whether a number $x$ is linearly independent of the current basis iterates through the set bits of $x$ using __builtin_ctzll(x) (or from bit $63$ down to $0$) and XORs with basis[b] in under 20 CPU clock cycles—making maximum-weight XOR basis queries practically instantaneous.

3. Complexity Matrix

Regret-Greedy Heap Rollback, Matroid Basis & CDCL SAT Implication Graph
O(N log N) Regret / 1-UIP CDCL

Trace Moore-Hodgson Regret-Greedy max-heap job eviction, weighted Matroid greedy basis construction, and CDCL SAT unit propagation + 1-UIP conflict clause learning.

Step 1 / 8
Custom Input:
Timeline

Invariant & State

Moore-Hodgson Regret Invariant: Accepting J2 pushes total time to 4+3=7 > d2=6. Pop max-duration job J1 (p=4) from heap to restore feasibility!
🌳 Live 2D Hierarchical Tree Topology — Regret-Greedy Heap Rollback, Matroid Basis & CDCL SAT Implication Graph Visited/Active Pivot/Root Resolved
Max p=4 J1 (p… [0] Total ti J2 (p… [1] Next J3 (p… [2]
[0]J1 (p=4, d=4)Max p=4 in Heap
[1]J2 (p=3, d=6)Total time=7 > d=6!
[2]J3 (p=2, d=7)Next
Before Eviction
Time = 7 > Deadline 6
Max-Heap Eviction
Drop J1 (p=4)
After Eviction
Time = 3 ≤ 6 (Feasible)
[Step 1] Deadline overflow at J2; max-heap evicts longest accepted job J1 (p=4), reducing total time from 7 to 3.
Matroid Type Rank $r = \max |I|$ Independence Oracle Cost Total Greedy Time Space Complexity
Uniform / Partition Matroid O(K) O(1) O(N log N) O(N)
Graphic Matroid (Kruskal Forest) V - 1 O(alpha(V)) O(E log E) O(V)
$\mathbb{F}_2$ Linear Matroid (XOR Basis, $B$ bits) B <= 64 O(B) O(N log N + N * B) O(B)
Unit-Time Deadline Scheduling (DSU) min(N, max d_i) O(alpha(N)) O(N log N) O(N)

4. Reference C++20 Implementation

#include <algorithm>
#include <array>
#include <cstdint>
#include <numeric>
#include <vector>

// 1. Linear Matroid over GF(2): Maximum Weight Linearly Independent XOR Basis
struct WeightedVector {
    std::uint64_t mask;
    long long weight;
};

long long max_weight_xor_basis(std::vector<WeightedVector> elements) {
    // Rado-Edmonds Greedy: sort descending by weight
    std::sort(elements.begin(), elements.end(), [](const WeightedVector& a, const WeightedVector& b) {
        return a.weight > b.weight;
    });

    std::array<std::uint64_t, 64> basis{};
    long long total_weight = 0;

    auto try_insert = [&](std::uint64_t x) -> bool {
        for (int b = 63; b >= 0; --b) {
            if (!((x >> b) & 1ULL)) continue;
            if (basis[b] == 0) {
                basis[b] = x;
                return true; // x is linearly independent of current basis!
            }
            x ^= basis[b];
        }
        return false; // x reduced to 0 -> linearly dependent
    };

    for (const auto& elem : elements) {
        if (elem.weight <= 0) break;
        if (try_insert(elem.mask)) {
            total_weight += elem.weight;
        }
    }
    return total_weight;
}

// 2. Unit-Time Deadline Scheduling Matroid with DSU Slot Allocation
struct DeadlineJob {
    int deadline; // 1-indexed deadline in [1..n]
    long long profit;
};

long long max_profit_deadline_scheduling(std::vector<DeadlineJob> jobs, int max_deadline) {
    std::sort(jobs.begin(), jobs.end(), [](const DeadlineJob& a, const DeadlineJob& b) {
        return a.profit > b.profit;
    });

    // parent[t] points to the latest available time slot <= t (0 means no slot free)
    std::vector<int> parent(max_deadline + 1);
    std::iota(parent.begin(), parent.end(), 0);

    auto find_free_slot = [&](auto& self, int t) -> int {
        return parent[t] == t ? t : (parent[t] = self(self, parent[t]));
    };

    long long total_profit = 0;
    for (const auto& job : jobs) {
        if (job.profit <= 0) break;
        int d = std::min(job.deadline, max_deadline);
        int free_slot = find_free_slot(find_free_slot, d);
        if (free_slot > 0) {
            total_profit += job.profit;
            // Occupy free_slot by merging it into free_slot - 1
            parent[free_slot] = find_free_slot(find_free_slot, free_slot - 1);
        }
    }
    return total_profit;
}

Production Pitfall — Confusing Maximum Weight Independent Set with Maximum Weight Base

When weights can be negative, the problem of finding a maximum-weight independent set stops adding elements as soon as $w(e_i) \le 0$. However, if the problem requires a maximum-weight basis (maximal independent set of size $\text{rank}(E)$, such as a Spanning Tree where negative edges are mandatory to connect the graph), you must continue scanning all elements until $|S| = \text{rank}(E)$ even when $w(e_i) < 0$.

Check yourself Question 1 of 4
Concept Check The Rado-Edmonds Theorem recall

What does the Rado-Edmonds Theorem state regarding a hereditary independence system $M = (E, \mathcal{I})$?

⚡ Interactive Challenge · Staff Level

Algorithmic Design & Complexity Check

Analyze the invariant, asymptotic bound, and hardware behavior before selecting.

In the Unit-Time Deadline Scheduling Matroid ($t_i = 1$, deadline $d_i$, profit $p_i$), we sort jobs by profit descending and assign each job to the latest available time slot $s \le d_i$ using Disjoint Set Union (`parent[s] = find(s - 1)`). Why does scheduling job $i$ as late as possible ($s \le d_i$) preserve matroid independence?

Key Takeaways

A matroid (E, I) satisfies non-emptiness, downward closure (hereditary), and the exchange axiom (if |A| < |B|, some x in B \ A can augment A).
The Rado-Edmonds Theorem proves that sorting by weight descending and greedily adding independent elements is optimal for all weight functions iff (E, I) is a matroid.
Kruskal's MST algorithm is the Rado-Edmonds greedy algorithm on the Graphic Matroid; Gaussian XOR elimination is the same algorithm on the F_2 Linear Matroid.
Unit-time job scheduling with deadlines and profits forms a matroid whose independence check runs in O(alpha(N)) per job via Disjoint Set Union.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue