Agents rank partners on the other side; a matching is stable if no pair prefer each other to their assigned partners (a blocking pair). Gale–Shapley always finds one in , and the side that proposes gets its best possible outcome. One-sided “roommates” instances may have no stable matching; Irving’s algorithm decides.
Gale–Shapley (with incomplete lists and capacities)
vector<int> galeShapley(int m, const vector<vector<int>>& pref, const vector<vector<int>>& rnk, vector<int> cap = {}) {
// pref[p]: acceptors in p's order (incomplete allowed); rnk[a][p]: a's rank of p, INT_MAX = unacceptable
int n = sz(pref); if (cap.empty()) cap.assign(m, 1);
vector<int> nxt(n), res(n, -1), fr(n); iota(all(fr), 0);
vector<set<pair<int, int>>> held(m); // {rank, proposer}
while (sz(fr)) {
int p = fr.back(); fr.pop_back();
if (nxt[p] == sz(pref[p])) continue; // list exhausted: p stays single
int a = pref[p][nxt[p]++];
if (rnk[a][p] == INT_MAX || !cap[a]) { fr.push_back(p); continue; }
held[a].insert({rnk[a][p], p});
if (sz(held[a]) > cap[a]) { auto w = prev(held[a].end()); fr.push_back(w->second); held[a].erase(w); }
}
for (int a = 0; a < m; a++) for (auto [r, p] : held[a]) res[p] = a;
return res; // partner of each proposer, -1 if none
}| Property (proposers propose to acceptors ) | |
|---|---|
| terminates after at most $\sum_p | \text{pref}_p |
| the result is stable | always exists in the two-sided case |
| proposer-optimal: every proposer gets their best partner over all stable matchings | the proposing side wins |
| acceptor-pessimal: every acceptor gets their worst stable partner | |
| rural hospitals: the same agents are matched in every stable matching (and a hospital that is not full gets the same residents) | |
| stable matchings form a distributive lattice (proposer-wise max / min of two stable matchings is stable) | |
| truth-telling is optimal for proposers (no proposer gains by lying); acceptors can gain | |
| many-to-one (hospitals–residents): residents propose, hospitals keep their best | the same theorems via cloning |
Proof: termination, stability, optimality, rural hospitals
Termination: a proposer never proposes twice to the same acceptor. Stability: let prefer to ‘s final partner (or is single but finds acceptable). Then proposed to and was rejected, then or later, in favour of agents ranks higher; an acceptor’s held set only improves, so at the end holds agents (one, in the one-to-one case) all better than . So does not block.
Proposer-optimal. Call achievable for if some stable matching pairs them. Suppose the run ever rejects by an achievable , and look at the first such rejection, in favour of whom prefers. Let be stable with ; then is achievable for . Up to now no proposer was rejected by an achievable partner, so has not been rejected by , and since proposers go down their lists, prefers to . Then blocks . Contradiction: no proposer is ever rejected by an achievable partner, so each ends with the best achievable one (the result is itself stable, so its partner is achievable).
Acceptor-pessimal. Let be the result and stable with preferring to . Then prefers (its best achievable partner) to , so blocks .
Rural hospitals (one-to-one). Every proposer matched in some stable is matched in (its partner there is at least as good as in ), so matches at least as many proposers; every acceptor matched in is matched in (its partner is its worst stable one), so matches at least as many acceptors. Both matchings have equal sizes on both sides, so the matched sets coincide. Many-to-one: clone hospital into copies with ‘s preferences, which residents rank consecutively in place of ; stable matchings correspond, and the algorithm above simulates proposals to the clones.
Lattice (complete lists). Let be stable and give each proposer the better of its two partners. Two proposers cannot get the same : if with preferring to and preferring to , stability of makes prefer to , and stability of makes prefer to . Each acceptor then gets the worse of its two partners (if is ‘s choice, prefers to , so prefers to ). A blocking pair of the result: prefers to both of its partners, prefers to its partner in the matching ( or ) that gave it, so blocks that matching. Contradiction.
Proof: proposers cannot gain by lying (Dubins–Freedman)
Blocking lemma. Let be the proposer-optimal stable matching for the true preferences, any matching in which everyone is matched acceptably, and the nonempty set of proposers who strictly prefer to . Then some with blocks . Case : take with . Since prefers to and is stable, prefers to , and (as ); weakly prefers to , so blocks . Case : each rejected during the run (that proposer reached its partner later). Let be the acceptor receiving its final proposal (from ) last; at that moment it already held someone, , who is rejected now. If , would later propose to , contradicting the choice of ; so . proposed to before , so prefers to ; and held after having rejected , so prefers to . So blocks .
Strategy-proofness. Suppose proposer misreports and the algorithm returns , which truly prefers to . is stable for the reported profile, so no pair without blocks it under true preferences (those agents reported truthfully). Applying the lemma with true preferences, a blocking pair with exists; since , , contradicting stability under the reports.
Stable roommates (Irving)
One set of agents (even), complete strict lists. Phase 1 runs proposals and deletes from each receiver’s list everyone worse than the proposer it holds; phase 2 repeatedly finds a rotation (follow “second choice’s current holder” until a cycle) and eliminates it. An empty list means no stable matching. entries, worst case in this simple form.
vector<int> stableRoommates(const vector<vector<int>>& rnk) { // rnk[i][j]: position of j in i's list; {} if none
int n = sz(rnk); if (n % 2) return {};
vector<vector<char>> act(n, vector<char>(n)); vector<deque<int>> pref(n);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) if (i != j) pref[i].push_back(j), act[i][j] = 1;
sort(all(pref[i]), [&](int x, int y) { return rnk[i][x] < rnk[i][y]; });
}
vector<int> propose(n, -1), proposed(n, -1); bool ok = true;
auto rem = [&](int x, int y) { act[x][y] = act[y][x] = 0; };
auto clip = [&](int x) { // drop deleted entries at both ends
while (sz(pref[x]) && !act[x][pref[x].front()]) pref[x].pop_front();
while (sz(pref[x]) && !act[x][pref[x].back()]) pref[x].pop_back();
if (pref[x].empty()) ok = false;
};
auto add = [&](int x, int y) { // y holds x: delete everyone y likes less
propose[x] = y, proposed[y] = x;
while (ok) { clip(y); if (!ok) return; if (pref[y].back() != x) rem(y, pref[y].back()); else break; }
};
auto nextOf = [&](int x) { // holder of x's second choice
clip(x); int y = pref[x].front(); pref[x].pop_front(); clip(x);
int sec = pref[x].front(); pref[x].push_front(y);
return proposed[sec];
};
queue<int> q; for (int i = 0; i < n; i++) q.push(i);
while (sz(q) && ok) { // phase 1: proposals
int x = q.front(); q.pop();
while (ok) {
clip(x); if (!ok) break;
int y = pref[x].front(), X = proposed[y];
if (X != -1 && rnk[y][X] < rnk[y][x]) { rem(x, y); continue; }
if (X != -1) propose[X] = -1, proposed[y] = -1, q.push(X);
add(x, y); break;
}
}
if (!ok) return {};
for (int cur = 0; ok;) { // phase 2: eliminate rotations
for (; cur < n; cur++) { clip(cur); if (!ok) return {}; if (sz(pref[cur]) > 1) break; }
if (cur == n) break;
vector<int> cyc, CYC; int x = cur, y = cur;
do { x = nextOf(x); y = nextOf(nextOf(y)); } while (x != y && ok);
if (!ok) return {};
do { cyc.push_back(y); y = nextOf(y); } while (x != y);
for (int a : cyc) { int b = propose[a]; CYC.push_back(b); propose[a] = -1, proposed[b] = -1; rem(a, b); }
for (int i = 0; i < sz(cyc) && ok; i++) add(cyc[i], CYC[(i + 1) % sz(cyc)]);
}
if (!ok) return {};
return propose;
}Proof sketch: Irving's algorithm
Phase 1 is safe: when holds ‘s proposal, any agent that ranks below cannot be ‘s partner in a stable matching: by induction no stable matching pairs with someone ranks above (those rejected or deleted safely), so in a stable matching with , would prefer to its partner while prefers to , a blocking pair. Phase 2 is safe: a rotation (with first on ‘s list and second) can be eliminated (each moves to , deleting worse entries) without destroying all stable matchings: if a stable matching exists in the table, one exists after elimination (Irving’s lemma, by an exchange argument showing that a stable matching using some pair can be transformed). End: when every list has length one, the table pairs everyone and the pairing is stable (all potential blocking pairs were deleted with a justification). An emptied list certifies non-existence.
Notes and variants
- With ties, weak stability always exists (break ties arbitrarily) but maximising its size is NP-hard; with couples, existence is NP-hard.
- Egalitarian (minimise total rank) and minimum-regret stable marriages are polynomial: stable matchings correspond to closed subsets of the rotation poset (Irving–Leather), with weights adding over rotations, so the egalitarian one is a maximum weight closure (Flow Modeling).
- The number of stable matchings can be exponential; counting is p-complete.
- A popular special case: when one side’s preferences are derived from a common score (e.g. all hospitals rank by exam score), the stable matching is the serial dictatorship: process residents by score, each takes their best hospital with space.
- The lattice of stable matchings has a well-defined median (coordinate-wise median of any odd number of stable matchings is itself stable), a middle ground between the proposer-optimal and acceptor-optimal extremes.
- This is not just a curiosity: the many-to-one version runs the US medical residency match (NRMP, ~40,000 applicants/year), New York and Boston school choice, and is the starting point for kidney-exchange matching; Shapley and Roth shared the 2012 Nobel Prize in Economics for this line of work.