This page covers from maximum weight perfect matching in general graphs to maximum weight matching in general graphs (maximum weight matching can be transformed into maximum weight perfect matching by adding zero-weight edges).
Prerequisites
Blossom
The difference between matching in general graphs and bipartite graphs is that general graphs may contain odd cycles. Even cycles can be treated as bipartite graphs.
The Blossom Algorithm handles odd cycles by contracting them into a blossom and setting all vertices in the blossom as even vertices. Since all vertices in the blossom can become even vertices, the entire blossom can be contracted into a single even vertex. Note that a blossom may contain other blossoms.
This can also be transformed into a linear programming and dual problem, but some processing of blossoms is required.
Vertex Labeling and Equality Edge
Define \(z_u\) as the vertex labeling of vertex \(u\), with the same meaning as in the KM algorithm. Define an edge \(e(u,v)\) as an "equality edge" if and only if the sum of the labels of vertices \(u\) and \(v\) equals the weight of edge \(e\) (\(z_u + z_v = w(e)\)). In this case, the edge label \(z_e = z_u + z_v − w(e) = 0\).
Linear Programming for Maximum Weight Perfect Matching in General Graphs
Definitions
Since a blossom has at least three vertices, it contracts into a single point. Let \(O\) be the set of all odd-sized sets (including all blossoms) with size \(\geq 3\), and \(\gamma(S)\) denote the edges within set \(S\).
\[
\begin{aligned}
& \text{Let } S\subseteq V \\
& \gamma(S)=\{(u,v)\in E:u\in S,v\in S\} \\
& O=\{B\subseteq V:|B|\text{ is odd and }|B|\geq3\} \\
\end{aligned}
\]
Dual Problem
Primal Problem
\[
\begin{aligned}
& \max\sum_{e\in E}w(e)x_e \\
& \text{Constraints:} \\
& x(\delta(u))=1:\forall u\in V \\
& x(\gamma(B))\leq\lfloor\frac{|B|}{2}\rfloor:\forall B\in O \\
& x_e\geq0:\forall e\in E \\
\end{aligned}
\]
Then, the problem is transformed into the dual problem via Primal-Dual.
Edges with \(x_e=1\) are matching edges, and edges with \(x_e=0\) are non-matching edges. As in bipartite graphs, we must satisfy \(x_e\in\{0,1\}:\forall e\in E\). Therefore, in maximum weight perfect matching, all matching edges must be equality edges.
Unlike bipartite graphs, general graphs have the additional \(z_B\) to handle. Below, we consider when \(z_B > 0\).
It can be seen that it is best to keep \(z_B=0\) whenever possible, but sometimes \(z_B>0\) is necessary. \(z_B>0\) is meaningful only when \(x(\gamma(B)) = \left\lfloor \dfrac{|B|}2 \right\rfloor \text{and} x(\delta(B)) = 1\). In other cases, \(z_B>0\) is meaningless.
According to complementary slackness, we have the following correspondences:
For selected edges \(e\), we must have \(z_e=0\).
\[
x_e>0 \longrightarrow z_e=0,\quad \forall e\in E
\]
For selected sets \(B\), \(z_B>0 \longrightarrow x(\gamma(B))= \left\lfloor \dfrac{|B|}2 \right\rfloor\), i.e., all sets \(B\) with \(z_B>0\) have exactly half of their edges selected, meaning \(B\) is a blossom, and an edge within the blossom is selected for augmentation. Additionally, we add a condition: \(x(\delta(B))=1\), i.e., \(z_B>0\) is meaningful only when blossom \(B\) has exactly one outgoing edge.
\[
z_B>0 \longrightarrow x(\gamma(B))=\left\lfloor\frac{|B|}2\right\rfloor, x(\delta(B))=1\quad \forall B\in O
\]
Using the concept of equality edges, combined with the previous Blossom Algorithm: augmenting paths composed of "equality edges" are continuously expanded. Since all edges used for expansion are "equality edges," the resulting maximum weight perfect matching will still consist entirely of "equality edges."
Handling Blossoms
When a blossom is encountered, it is contracted into an even vertex. All vertices in the blossom are set as even vertices, and its \(z_B\) is set to \(0\).
Since blossoms are stored after contraction and are only expanded under certain conditions, they cannot be recorded using previous methods.
Unless otherwise specified, all mentioned vertices include even vertices formed by contracted blossoms.
Since blossoms may also be contracted into points and added to the queue, and the number of blossoms is not fixed, we cannot enumerate each point to check for augmenting paths as before. Therefore, during Breadth-First Search (BFS), all unmatched vertices must be placed in the queue.
This will generate multiple alternating trees simultaneously.
The Four Steps of the Algorithm
This algorithm can be divided into four steps.
GROW (Equality Edge): Form alternating trees using "equality edges."
AUGMENT (Augment): Find augmenting paths and expand the matching.
SHRINK (Shrink): Contract a blossom into a single point.
EXPAND (Expand): Expand a blossom.
During the AUGMENT phase, since all unmatched vertices are in different alternating trees, when an even vertex from one alternating tree connects to an even vertex from another, it indicates an augmenting path has been found.
No Equality Edge for Expansion
As in bipartite graphs, there may be situations where no "equality edge" can be found for expansion. In such cases, vertex labeling needs to be adjusted.
Adjusting VERTEX LABELING
Vertex labeling must still maintain the greater-than-or-equal property, existing "equality edges" must not be changed, and \(z_B\) should be minimized as much as possible.
Definition of Odd/Even Vertices
Let \(u^−\) denote that \(u\) is an odd vertex in the alternating tree.
Let \(u^+\) denote that \(u\) is an even vertex in the alternating tree.
Let \(u^\varnothing\) denote that \(u\) is not in any alternating tree.
All subsequent mentions of \(B\) assume it is a blossom and also represent the contracted point.
Blossoms can also be odd or even, so symbols like \(B^+\), \(B^−\), \(B^\varnothing\) are also applicable.
Suppose there are currently r alternating trees \(T_i=(U_{t_i},V_{t_i}):1\leq i\leq r\), and let
If \(z_B=0\) (i.e., \(d=d3\)), to prevent \(z_B<0\), the blossom must be expanded (EXPAND).
After expansion, only the alternating path within the blossom is retained, and vertices in the blossom not on the alternating path are set as unvisited (\(\varnothing\)).
This creates one (or more) equality edges, keeps existing equality edges unchanged, maintains the property \(z_e\geq0:\forall e\in E\), minimally increases \(z_B\), and allows the search for augmenting paths to continue.
Maximum Weight Matching in General Graphs
The above solves maximum weight perfect matching. To solve maximum weight matching, an additional constraint must be added to vertex labeling: for all matched vertices \(u\), \(z_u>0\).
Initially, set all \(z_u=max(\{w(e):e\in E\})/2\).
Vertices with vertex labeling \(0\) will eventually become unmatched vertices.
Reference Code
For ease of implementation, edge weights are multiplied by \(2\) to compute \(z_e\), avoiding floating-point errors.
Storage
1 2 3 4 5 6 7 8 91011121314151617181920
constexprintINF=INT_MAX;constexprintMAXN=400;structedge{intu,v,w;// Represents an edge (u,v) with weight wedge(){}edge(intu,intv,intw):u(u),v(v),w(w){}};intn,n_x;// There are n vertices, numbered 1 ~ n// n_x represents the current number of vertices plus blossoms, numbered from n+1 to n_x as blossom nodesedgeg[MAXN*2+1][MAXN*2+1];// The graph is stored as an adjacency matrix. Since there are at most n-1 blossoms, the size is MAXN*vector<int>flower[MAXN*2+1];// flower[b] records which vertices are in blossom b// We record vertices in a blossom by only recording the outermost blossom
intlab[MAXN*2+1];// lab[u] records z_u, lab[b] records z_Bintmatch[MAXN*2+1],slack[MAXN*2+1],st[MAXN*2+1],pa[MAXN*2+1];// match[x]=y indicates (x,y) is a matching edge, where x and y may be blossoms// slack[x]=u indicates z(x,u) is the smallest edge among all edges adjacent to x// Indicates the blossom containing node x is b. If x=b and b<=n, then x// is an ordinary node (not part of any blossom) Indicates in the alternating tree, the parent node of v is uintflower_from[MAXN*2+1][MAXN+1],S[MAXN*2+1],vis[MAXN*2+1];/*flower_from[b][x]=xs indicates the largest sub-blossom of b containing x is xsx is a point in b, xs is a blossom or a point in b, and x=xs or x is one of the points in xs*/// S[u]={-1:unvisited 0:even vertex 1:odd vertex}// vis is only used during LCA search to check if a node has been visitedqueue<int>q;// Queue used for BFS to find augmenting paths
12345
flower_from[b2][6] = b1
flower_from[b2][5] = b1
flower_from[b2][9] = 9
flower_from[b1][6] = 6
And so on...
1 2 3 4 5 6 7 8 910111213141516171819202122
inte_delta(constedge&e){// Compute ze. For convenience, all edge weights are multiplied by two.// Directly computing e_delta inside a blossom may cause errors.returnlab[e.u]+lab[e.v]-g[e.u][e.v].w*2;}voidupdate_slack(intu,intx){// Update slack[x] using uif(!slack[x]||e_delta(g[u][x])<e_delta(g[slack[x]][x])){slack[x]=u;}}voidset_slack(intx){// Compute slack[x]. slack[x]=0 indicates x is a node in the alternating tree.slack[x]=0;for(intu=1;u<=n;++u){if(g[u][x].w>0&&st[u]!=x&&S[st[u]]==0){update_slack(u,x);}}}
1 2 3 4 5 6 7 8 910111213141516171819202122
voidq_push(intx){// Push x into the queue. We assume the queue cannot directly push a blossom.if(x<=n)q.push(x);else{// To push a blossom, all original vertices in the blossom must be added to the queue.for(size_ti=0;i<flower[x].size();i++){q_push(flower[x][i]);}}}voidset_st(intx,intb){// Set the blossom containing x to bst[x]=b;if(x>n){// If x is also a blossom, all vertices inside x must have their containing blossom set to b.for(size_ti=0;i<flower[x].size();++i){set_st(flower[x][i],b);}}}
1 2 3 4 5 6 7 8 910111213
intget_pr(intb,intxr){// xr is a point in flower[b]. The return value pr is its position.// For convenience, we ensure flower[b][0]~flower[b][pr] is the alternating path in the blossom.intpr=find(flower[b].begin(),flower[b].end(),xr)-flower[b].begin();if(pr%2==1){// Check its position in the blossom. If flower[b][0]~flower[b][pr] is not an alternating path,// reverse the entire blossom and recompute pr.// Ensure flower[b][0]~flower[b][pr] is the alternating path in the blossom.reverse(flower[b].begin()+1,flower[b].end());return(int)flower[b].size()-pr;}elsereturnpr;}
If get_pr(b2,11) is used, flower[b2] becomes {9,10,11,2,3,4,b1}, and returns 2.
If get_pr(b2,2) is used, flower[b2] becomes {9,b1,4,3,2,11,10}, and returns 4.
voidset_match(intu,intv){// Set u and v as a matching edge. u and v may be blossoms.match[u]=g[u][v].v;if(u>n){// If u is a blossomedgee=g[u][v];intxr=flower_from[u][e.u];// Find which sub-blossom in flower[u] contains e.uintpr=get_pr(u,xr);// Find the position of xr and ensure 0~pr is the alternating path in the blossomfor(inti=0;i<pr;++i){// Flip matching and non-matching edges along the alternating path in the blossomset_match(flower[u][i],flower[u][i^1]);}set_match(xr,v);// Set (xr,v) as a matching edgerotate(flower[u].begin(),flower[u].begin()+pr,flower[u].end());// Finally, set pr as the base of the blossom. Since the blossom is stored with flower[u][0] as the base,// rotate flower[u][pr] to the front.}}voidaugment(intu,intv){// Augment u and all its ancestors, and set (u,v) as a matching edge.for(;;){intxnv=st[match[u]];set_match(u,v);if(!xnv)return;set_match(xnv,st[pa[xnv]]);u=st[pa[xnv]];v=xnv;}}intget_lca(intu,intv){// Find the LCA of u and v in the alternating tree.staticintt=0;for(++t;u||v;swap(u,v)){if(u==0)continue;if(vis[u]==t)returnu;vis[u]=t;// This method avoids clearing the vis array.u=st[match[u]];if(u)u=st[pa[u]];}return0;}
voidadd_blossom(intu,intlca,intv){// Contract the blossom formed by u, v, and lca into a single point b.// The LCA of u and v in the alternating tree becomes the base of the blossom.intb=n+1;while(b<=n_x&&st[b])++b;if(b>n_x)++n_x;// Find the currently unused blossom ID.lab[b]=0;// Set zB=0S[b]=0;// The entire blossom is an even vertex.match[b]=match[lca];// Set the matching edge of the blossom to the matching edge of the base.flower[b].clear();flower[b].push_back(lca);for(intx=u,y;x!=lca;x=st[pa[y]]){flower[b].push_back(x);y=st[match[x]];flower[b].push_back(y);q_push(y);}reverse(flower[b].begin()+1,flower[b].end());for(intx=v,y;x!=lca;x=st[pa[y]]){flower[b].push_back(x);y=st[match[x]];flower[b].push_back(y);q_push(y);}// All points in b are added to flower[b] in a circular manner, with the base as the first element.set_st(b,b);// Set the containing blossom of all elements in the blossom to b.for(intx=1;x<=n_x;++x){g[b][x].w=0;g[x][b].w=0;}for(intx=1;x<=n;++x){flower_from[b][x]=0;}for(size_ti=0;i<flower[b].size();++i){intxs=flower[b][i];for(intx=1;x<=n_x;++x){// Set the edge between b and x as the edge with the smallest e_delta among all edges adjacent to xs.if(g[b][x].w==0||e_delta(g[xs][x])<e_delta(g[b][x])){g[b][x]=g[xs][x];g[x][b]=g[x][xs];}}for(intx=1;x<=n;++x){if(flower_from[xs][x]){// If point xs in b contains x,// then flower_from[b][x] will be xs.flower_from[b][x]=xs;}}}set_slack(b);// Finally, the slack value for b must be set.}
voidexpand_blossom(intb){// When b is an odd blossom and zB=0, it must be expanded.// Since only b is expanded, if b contains other blossoms,// they do not need to be expanded.for(size_ti=0;i<flower[b].size();++i){set_st(flower[b][i],flower[b][i]);// First, set the containing blossom of each element in flower[b] to itself.}intxr=flower_from[b][g[b][pa[b]].u];// xr indicates which sub-blossom in flower[b] contains the parent node of b in the alternating tree.intpr=get_pr(b,xr);// Find the position of xr and ensure 0~pr is the alternating path in the blossom.for(inti=0;i<pr;i+=2){// Expand the alternating path into the alternating tree.// Add even vertices in the alternating path to the queue.intxs=flower[b][i];intxns=flower[b][i+1];pa[xs]=g[xns][xs].u;S[xs]=1;S[xns]=0;slack[xs]=0;set_slack(xns);q_push(xns);}S[xr]=1;// At this point, xr will be an odd vertex or odd blossom.pa[xr]=pa[b];for(size_ti=pr+1;i<flower[b].size();++i){// Set all vertices in the blossom not on the alternating path as unvisited.intxs=flower[b][i];S[xs]=-1;set_slack(xs);}st[b]=0;}
boolon_found_edge(constedge&e){// When an equality edge e is found during BFS,// the following processing must be performed.// Here u must be an even vertex.intu=st[e.u],v=st[e.v];if(S[v]==-1){// v is an unvisited vertex.pa[v]=e.u;S[v]=1;intnu=st[match[v]];slack[v]=0;slack[nu]=0;S[nu]=0;q_push(nu);}elseif(S[v]==0){// v is an even vertex.intlca=get_lca(u,v);if(!lca){// lca=0 indicates u and v are in different alternating trees, an augmenting path exists.augment(u,v);augment(v,u);returntrue;// Augmenting path found.}elseadd_blossom(u,lca,v);// Otherwise, u and v are in the same tree and form a blossom, which must be contracted.}returnfalse;}
boolmatching(){memset(S+1,-1,sizeof(int)*n_x);memset(slack+1,0,sizeof(int)*n_x);q=queue<int>();// Clear the queue.for(intx=1;x<=n_x;++x){if(st[x]==x&&!match[x]){// Add all unmatched vertices to the queue and set them as even vertices.pa[x]=0;S[x]=0;q_push(x);}}if(q.empty())returnfalse;// All vertices are matched.for(;;){while(q.size()){// BFSintu=q.front();q.pop();if(S[st[u]]==1)continue;for(intv=1;v<=n;++v){if(g[u][v].w>0&&st[u]!=st[v]){if(e_delta(g[u][v])==0){if(on_found_edge(g[u][v]))returntrue;}elseupdate_slack(u,st[v]);}}}// Adjust lab values.intd=INF;for(intu=1;u<=n;++u){// This prevents lab<0.// If any lab[u]=0, terminate.if(S[st[u]]==0)d=min(d,lab[u]);}for(intb=n+1;b<=n_x;++b){if(st[b]==b&&S[b]==1)d=min(d,lab[b]/2);}for(intx=1;x<=n_x;++x)if(st[x]==x&&slack[x]){if(S[x]==-1)d=min(d,e_delta(g[slack[x]][x]));elseif(S[x]==0)d=min(d,e_delta(g[slack[x]][x])/2);}for(intu=1;u<=n;++u){if(S[st[u]]==0){if(lab[u]==d)returnfalse;// If lab[u]=0, terminate immediately.lab[u]-=d;}elseif(S[st[u]]==1)lab[u]+=d;}for(intb=n+1;b<=n_x;++b){if(st[b]==b){if(S[st[b]]==0)lab[b]+=d*2;elseif(S[st[b]]==1)lab[b]-=d*2;}}q=queue<int>();// Clear the queue.for(intx=1;x<=n_x;++x){// Check if any augmenting paths have been created.if(st[x]==x&&slack[x]&&st[slack[x]]!=x&&e_delta(g[slack[x]][x])==0)if(on_found_edge(g[slack[x]][x]))returntrue;}for(intb=n+1;b<=n_x;++b){// EXPAND operation: expand all odd blossoms with lab[b]=0.if(st[b]==b&&S[b]==1&&lab[b]==0)expand_blossom(b);}}returnfalse;}
pair<longlong,int>weight_blossom(){// Main function. Initialize first.memset(match+1,0,sizeof(int)*n);n_x=n;// Initially, there are no blossoms.intn_matches=0;longlongtot_weight=0;for(intu=0;u<=n;++u){// First, set the containing blossom of each vertex to itself.st[u]=u;flower[u].clear();}intw_max=0;for(intu=1;u<=n;++u)for(intv=1;v<=n;++v){// When u is a point, the only point it contains is itself.flower_from[u][v]=(u==v?u:0);w_max=max(w_max,g[u][v].w);// Find the maximum edge weight.}for(intu=1;u<=n;++u)lab[u]=w_max;// Set all lab to the maximum edge weight.// Since edge weights are multiplied by two to compute ze, no division by two is needed.while(matching())++n_matches;for(intu=1;u<=n;++u)if(match[u]&&match[u]<u)tot_weight+=g[u][match[u]].w;returnmake_pair(tot_weight,n_matches);}
Initialization
Very important. Must be initialized before use.
123456
voidinit_weight_graph(){// Must initialize before inputting edges into the graph.// Since it's maximum weight matching, non-existent edges are set to 0.for(intu=1;u<=n;++u)for(intv=1;v<=n;++v)g[u][v]=edge(u,v,0);}
Complexity Analysis
Each blossom is contracted or expanded at most once per BFS. Each contraction or expansion takes \(O(|V|)\) time. There are at most \(O(|V|)\) blossoms, so blossom processing takes \(O(|V|^2)\) time. BFS takes \(O(|V| + |E|)\) time. Therefore, finding an augmenting path takes \(O(|V| + |E|) + O(|V|^2) = O(|V|^2)\) time.
At most \(|V|\) BFS operations are performed. Thus, the total time complexity is \(O(|V|^3)\).