import java.util.Enumeration; import java.io.*; /** A repræsentation of a bipartite graph. The graph is represented only by the nodes - the edges are not stored explicit in the graph. The nodes are put into two lists, <i>V1</i> and <i>V2</i>. @author Martin Geisler <gimpster@gimpster.com> @author Jérémy Auneau <jeremy.auneau@skjoldhoej.dk> */ public class BipartiteGraph { private Node[] v1 = new Node[0]; private Node[] v2 = new Node[0]; private int n1 = 0; private int n2 = 0; /** Run the graph coloring algorithm. The graph is colored by application of the coloring transition system. The algorithm runs in <i>O</i>(<i>|m| + |n|</i>), where <i>m</i> and <i>n</i> are the edges and nodes from the graph which nodes are those from <i>V1</i> and <i>V2</i>. @return a random terminal node */ public Node runTransitionSystem() { Sequence m1 = new Sequence(); // Edges from V1 to V2 Sequence m2 = new Sequence(); // Edges from V2 to V1 Node v0 = null; // A random initial node. for (int i = 0; i < n1; i++) { if (v1[i].inDegree() == 0) { v1[i].color = "black"; Enumeration e = v1[i].outEdges(); while (e.hasMoreElements()) { m1.insertLast((Edge)e.nextElement()); } } } while (m1.size() != 0 || m2.size() != 0) { while (m1.size() != 0) { Edge e = (Edge)m1.removeLast(); if (e.to.outDegree() == 0) { e.to.color = "black"; /* We pick a random terminal node: */ v0 = e.to; /* We also store the edge from which we came, so that we can find our way back again later: */ e.to.prev = e; } else { e.to.color = "gray"; /* We store the node which we came from, so that we can find our way back again later: */ e.to.prev = e; Edge edge = e.to.anyOutEdge(); // There is exactly one. if (edge.to.color.equals("white")) { m2.insertLast(edge); } } } while (m2.size() != 0) { Edge e = (Edge)m2.removeLast(); e.to.color = "gray"; Enumeration enum = e.to.outEdges(); while (enum.hasMoreElements()) { Edge edge = (Edge)enum.nextElement(); if (edge.to.color.equals("white")) { m1.insertLast(edge); } } } } return v0; } /** Turns the edges around. The edges are reorientated along a random path leading back from the terminal node <code>v0</code> to an initial node. It runs in <i>O</i>(<i>|m'|</i>) where <i>m'</i> are the edges that lie on the path. The terminal node <code>v0</code> <i>must</i> come from a call to {@link #runTransitionSystem} because that method also stores information needed to find a path back to an initial node. @param v0 a terminal node as returned by runTransitionSystem() @see #runTransitionSystem() */ public void turnEdges(Node v0) { Sequence m = new Sequence(); Edge e1 = null; // An Edge from V1 to V2 Edge e2 = null; // An Edge from V2 to V1 Node v1 = null; // A Node in V1 Node v2 = null; // A Node in V2 e1 = v0.prev; v1 = e1.from; m.insertLast(e1); while (!v1.color.equals("black")) { e2 = v1.anyInEdge(); // There is exactly one edge. v2 = e2.from; e1 = v2.prev; v1 = e1.from; m.insertLast(e1); m.insertLast(e2); } Enumeration e = m.elements(); while (e.hasMoreElements()) { Edge edge = (Edge)e.nextElement(); Node a = edge.from; Node b = edge.to; edge.remove(); new Edge(b, a); } } /** Colors the nodes white All the nodes composing the graph are run through. The color of the nodes is systematically changed to white. The algorithm runs in <i>O</i>(<i>|n|</i>), where <i>n</i> are the nodes listed in <i>V1</i> and <i>V2</i> (which means the total amount of nodes in the graph). */ public void removeColors() { for (int i = 0; i < n1; i++) v1[i].color = "white"; for (int i = 0; i < n2; i++) v2[i].color = "white"; } /** Loads an input graph from a file Input data are read from a file and the corresponding graph is constructed. The algorithm runs in <i>O</i>(<i>|n| + |m|</i>) where <i>m</i> are the edges listed in the input file and <i>|n|</i> the number of nodes composing the graph. @param filename an input data file describing a graph. */ public void loadDataFromFile(String filename) { try { BufferedReader input = new BufferedReader(new FileReader(filename)); n1 = Integer.parseInt(input.readLine()); v1 = new Node[n1]; for (int i = 0; i < n1; i++) v1[i] = new Node("A" + (i+1), i); n2 = Integer.parseInt(input.readLine()); v2 = new Node[n2]; for (int i = 0; i < n2; i++) v2[i] = new Node("B" + (i+1), i); int m = Integer.parseInt(input.readLine()); for (int i = 0; i < m; i++) { String line = input.readLine(); int s = line.indexOf(" ", 0); String a = line.substring(0, s); String b = line.substring(s+1, line.length()); new Edge(v1[Integer.parseInt(a)], v2[Integer.parseInt(b)]); } /* Extension: it is possible to specify the names of the nodes in the lines that follow the last edge. The format is: one name per line, starting with the names of the nodes in V1. */ for (int i = 0; i < n1 && input.ready(); i++) { v1[i].name = input.readLine(); } for (int i = 0; i < n2 && input.ready(); i++) { v2[i].name = input.readLine(); } } catch (IOException e) { System.err.println("There was an IO error while " + "reading the file - aborting..."); System.exit(1); } catch (NumberFormatException e) { System.err.println("There was a number format error while " + "reading the file - aborting..."); } } /** Produces a graphical LaTeX output of a graph. The output is an Xy-pic matrix which can be included in another LaTeX document. @param filename the LaTeX output will be saved here. */ public void toLaTeX(String filename) { toLaTeX(filename, false); } /** Produces a graphical LaTeX output of a graph. The output is an Xy-pic matrix which can be included in another LaTeX document. @param filename the LaTeX output will be saved here. @param only_pairs boolean that indicates whether the produced graph should contain all the edges or only the pair edges. */ public void toLaTeX(String filename, boolean only_pairs) { Enumeration enum = null; String str = "\\xymatrix@!0{\n"; int i = 0; int extra_columns = 8; while (i < n1 || i < n2) { if (i < n1) { str += " {\\rbox{" + v1[i].name + "\\;}" + "\\" + v1[i].color.charAt(0) + "box}"; if (!only_pairs) { enum = v1[i].outEdges(); while (enum.hasMoreElements()) { Edge e = (Edge)enum.nextElement(); Node n = e.to; str += " \\ar[" + (n.rankLaTeX - i) + "," + extra_columns + "] "; } } } str += " "; for (int c = 0; c < extra_columns; c++) { str += "&"; } str += " "; if (i < n2) { enum = v2[i].outEdges(); while (enum.hasMoreElements()) { Edge e = (Edge)enum.nextElement(); Node n = e.to; str += "\\ar@*{[thicker]}[" + (n.rankLaTeX - i) + ",-" + extra_columns + "] "; } str += "{\\" + v2[i].color.charAt(0) + "circ" + "\\lbox{\\;" + v2[i].name + "}}"; } i++; if ((i < n1 && n1 > n2) || (i < n2 && n2 > n1)) { str += " \\\\\n"; } } str = str + "\n}\n"; BufferedWriter out; try { out = new BufferedWriter(new FileWriter(filename)); out.write(str); out.close(); } catch (IOException e) { System.err.println("There was an IO problem with the file " + filename + " - no LaTeX output produced."); } } /** Returns a string representation of the graph. This should only be used when debugging. @return an enumeration of the nodes in <i>V1</i> and <i>V2</i>. */ public String toString() { Enumeration e = null; String result = "Nodes in V1:\n"; for (int i = 0; i < v1.length; i++) { result = result + v1[i] + ":\n In: "; e = v1[i].inEdges(); while (e.hasMoreElements()) result = result + (Edge)e.nextElement() + " "; result = result + "\n Out: "; e = v1[i].outEdges(); while (e.hasMoreElements()) result = result + (Edge)e.nextElement() + " "; result = result + "\n"; } result = result + "Nodes in V2:\n"; for (int i = 0; i < v2.length; i++) { result = result + v2[i] + ":\n In: "; e = v2[i].inEdges(); while (e.hasMoreElements()) result = result + (Edge)e.nextElement() + " "; result = result + "\n Out: "; e = v2[i].outEdges(); while (e.hasMoreElements()) result = result + (Edge)e.nextElement() + " "; result = result + "\n"; } return result; } /** Get a string representation of the edges that are part of the matching. This is done by finding the edges that goes from V2 to V1. It is done in <i>O</i>(<i>|V1|</i>) since we only have to ask each node in V1 once to find out if there is an incoming edge. If so, then it takes a constant amount of time to acquire the edge. @return a string representation of the edges in the matching, or the empty string if the matching is the empty set. */ public String getMatches() { String str = ""; for (int i = 0; i < n1; i++) { if (v1[i].inDegree() == 1) { str += ", " + v1[i].anyInEdge().toString(); } } return str.substring(2); } } // BipartiteGraph