1 /*
2  * Copyright (c) 2014 European Bioinformatics Institute (EMBL-EBI)
3  *                    John May <jwmay@users.sf.net>
4  *
5  * Contact: cdk-devel@lists.sourceforge.net
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as published by
9  * the Free Software Foundation; either version 2.1 of the License, or (at
10  * your option) any later version. All we ask is that proper credit is given
11  * for our work, which includes - but is not limited to - adding the above
12  * copyright notice to the beginning of your source code files, and to any
13  * copyright notice that you may distribute with programs based on this work.
14  *
15  * This program is distributed in the hope that it will be useful, but WITHOUT
16  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
18  * License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 U
23  */
24 
25 package org.openscience.cdk.graph;
26 
27 
28 import java.util.Arrays;
29 import java.util.BitSet;
30 
31 /**
32  * A matching is an independent edge set of a graph. This is a set of edges that
33  * share no common vertices. A matching is perfect if every vertex in the graph
34  * is matched. Each vertex can be matched with exactly one other vertex.
35  *
36  * This class provides storage and manipulation of a matching. A new match is
37  * added with {@link #match(int, int)}, any existing match for the newly matched
38  * vertices is no-longer available. The status of a vertex can be queried with
39  * {@link #matched(int)} and the matched vertex obtained with {@link
40  * #other(int)}.
41  *
42  * @author John May
43  * @cdk.module standard
44  * @see <a href="http://en.wikipedia.org/wiki/Matching_(graph_theory)">Matching
45  * (graph theory), Wikipedia</a>
46  */
47 public final class Matching {
48 
49     /** Indicate an unmatched vertex. */
50     private static final int NIL = -1;
51 
52     /** Match storage. */
53     private final int[]      match;
54 
55     /**
56      * Create a matching of the given size.
57      *
58      * @param n number of items
59      */
Matching(final int n)60     private Matching(final int n) {
61         this.match = new int[n];
62         Arrays.fill(match, NIL);
63     }
64 
65     /**
66      * Add the edge '{u,v}' to the matched edge set. Any existing matches for
67      * 'u' or 'v' are removed from the matched set.
68      *
69      * @param u a vertex
70      * @param v another vertex
71      */
match(final int u, final int v)72     public void match(final int u, final int v) {
73         // set the new match, don't need to update existing - we only provide
74         // access to bidirectional mappings
75         match[u] = v;
76         match[v] = u;
77     }
78 
79     /**
80      * Access the vertex matched with 'v'.
81      *
82      * @param v vertex
83      * @return matched vertex
84      * @throws IllegalArgumentException the vertex is currently unmatched
85      */
other(final int v)86     public int other(final int v) {
87         if (unmatched(v)) throw new IllegalArgumentException(v + " is not matched");
88         return match[v];
89     }
90 
91     /**
92      * Remove a matching for the specified vertex.
93      *
94      * @param v vertex
95      */
unmatch(final int v)96     public void unmatch(final int v) {
97         match[v] = NIL;
98     }
99 
100     /**
101      * Determine if a vertex has a match.
102      *
103      * @param v vertex
104      * @return the vertex is matched
105      */
matched(final int v)106     public boolean matched(final int v) {
107         return !unmatched(v);
108     }
109 
110     /**
111      * Determine if a vertex is not matched.
112      *
113      * @param v a vertex
114      * @return the vertex has no matching
115      */
unmatched(final int v)116     public boolean unmatched(final int v) {
117         return match[v] == NIL || match[match[v]] != v;
118     }
119 
120     /**
121      * Attempt to augment the matching such that it is perfect over the subset
122      * of vertices in the provided graph.
123      *
124      * @param graph  adjacency list representation of graph
125      * @param subset subset of vertices
126      * @return the matching was perfect
127      * @throws IllegalArgumentException the graph was a different size to the
128      *                                  matching capacity
129      */
perfect(int[][] graph, BitSet subset)130     public boolean perfect(int[][] graph, BitSet subset) {
131 
132         if (graph.length != match.length || subset.cardinality() > graph.length)
133             throw new IllegalArgumentException("graph and matching had different capacity");
134 
135         // and odd set can never provide a perfect matching
136         if ((subset.cardinality() & 0x1) == 0x1) return false;
137 
138         // arbitrary matching was perfect
139         if (arbitaryMatching(graph, subset)) return true;
140 
141         EdmondsMaximumMatching.maxamise(this, graph, subset);
142 
143         // the matching is imperfect if any vertex was
144         for (int v = subset.nextSetBit(0); v >= 0; v = subset.nextSetBit(v + 1))
145             if (unmatched(v)) return false;
146 
147         return true;
148     }
149 
150     /**
151      * Assign an arbitrary matching that covers the subset of vertices.
152      *
153      * @param graph  adjacency list representation of graph
154      * @param subset subset of vertices in the graph
155      * @return the matching was perfect
156      */
arbitaryMatching(final int[][] graph, final BitSet subset)157     boolean arbitaryMatching(final int[][] graph, final BitSet subset) {
158 
159         final BitSet unmatched = new BitSet();
160 
161         // indicates the deg of each vertex in unmatched subset
162         final int[] deg = new int[graph.length];
163 
164         // queue/stack of vertices with deg1 vertices
165         final int[] deg1 = new int[graph.length];
166         int nd1 = 0, nMatched = 0;
167 
168         for (int v = subset.nextSetBit(0); v >= 0; v = subset.nextSetBit(v + 1)) {
169             if (matched(v)) {
170                 assert subset.get(other(v));
171                 nMatched++;
172                 continue;
173             }
174             unmatched.set(v);
175             for (int w : graph[v])
176                 if (subset.get(w) && unmatched(w)) deg[v]++;
177             if (deg[v] == 1) deg1[nd1++] = v;
178         }
179 
180         while (!unmatched.isEmpty()) {
181 
182             int v = -1;
183 
184             // attempt to select a vertex with degree = 1 (in matched set)
185             while (nd1 > 0) {
186                 v = deg1[--nd1];
187                 if (unmatched.get(v)) break;
188             }
189 
190             // no unmatched degree 1 vertex, select the first unmatched
191             if (v < 0 || unmatched.get(v)) v = unmatched.nextSetBit(0);
192 
193             unmatched.clear(v);
194 
195             // find a unmatched edge and match it, adjacent degrees are updated
196             for (final int w : graph[v]) {
197                 if (unmatched.get(w)) {
198                     match(v, w);
199                     nMatched += 2;
200                     unmatched.clear(w);
201                     // update neighbors of w and v (if needed)
202                     for (final int u : graph[w])
203                         if (--deg[u] == 1 && unmatched.get(u)) deg1[nd1++] = u;
204 
205                     // if deg == 1, w is the only neighbor
206                     if (deg[v] > 1) {
207                         for (final int u : graph[v])
208                             if (--deg[u] == 1 && unmatched.get(u)) deg1[nd1++] = u;
209                     }
210                     break;
211                 }
212             }
213         }
214 
215         return nMatched == subset.cardinality();
216     }
217 
218     /**
219      * Create an empty matching with the specified capacity.
220      *
221      * @param capacity maximum number of vertices
222      * @return empty matching
223      */
withCapacity(final int capacity)224     public static Matching withCapacity(final int capacity) {
225         return new Matching(capacity);
226     }
227 
228     /**{@inheritDoc} */
229     @Override
toString()230     public String toString() {
231         StringBuilder sb = new StringBuilder(4 * match.length);
232         sb.append('[');
233         for (int u = 0; u < match.length; u++) {
234             int v = match[u];
235             if (v > u && match[v] == u) {
236                 if (sb.length() > 1) sb.append(", ");
237                 sb.append(u).append('=').append(v);
238             }
239         }
240         sb.append(']');
241         return sb.toString();
242     }
243 }
244