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 stablealways exists in the two-sided case
proposer-optimal: every proposer gets their best partner over all stable matchingsthe 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

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;
}

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.