Algorithm Implementation/Graphs/Maximum flow/Boykov & Kolmogorov
From Wikibooks, open books for an open world
Java [edit]
import java.util.LinkedList; /** * * @author bastien Jacquet * * Implements a graph to cut * Very complicated to program and debug * It worked for me, but it may still have bugs * See the paper for more details * Yuri Boykov, Vladimir Kolmogorov: An Experimental Comparison of Min-Cut/Max-Flow Algorithms for Energy Minimization in Vision. IEEE Trans. Pattern Anal. Mach. Intell. 26(9): 1124-1137 (2004) */ class Node { int index; int prevEdgeIndex; /* Edge to previous node of the path */ double maxCapToHere=0; //V2 int dist; } class Edge { int initial_vertex; /* initial vertex of this edge */ int terminal_vertex; /* terminal vertex of this edge */ double capacity; /* capacity */ double flow; int invEdgeIndex; } public class GraphCutBoykovKolmogorov { boolean debug=true; /** We assume that an edge with eps capacity is saturated */ private final double eps=1e-3; private int nbNode,nbEdges; //private int sourceNode=0,sinkNode=1; /* start node, terminate node */ private int w,h; private Node node[]; private Edge edge[]; /** startingEdge[nodeIndex] is an array of edges indexes that starts from node nodeIndex */ private int[][] startingEdge; /** Retrieve the node index for the (x,y) pixel. Inlined*/ private int indice(int x,int y){ return x*h+y+2; } /** Retrieve the starting edge index in the <b>startingEdge</b> array to the (x,y) pixel from source or sink. Inlined*/ private int indicePart(int x,int y){ return x*h+y; } /** * Construct an width x height Graph, with C-8 connectivity * It creates all the edges, with 0 capacity * @param width * @param height */ GraphCutBoykovKolmogorov (int width,int height){ w=width;h=height; // neighborhood=8; //int[][] voisins=new int[][]{{+1,0},{+1,-1},{+1,+1},{0,+1},{0,-1},{-1,+1},{-1,0},{-1,-1}}; int[][] voisinsEdgeACreer=new int[][]{{+1,0},{+1,-1},{0,-1},{-1,-1}}; this.nbNode=w*h+2;this.nbEdges=w*h*4+(h-2)*(w-2)*8+2*5*(h+w-4)+4*3; node=new Node[this.nbNode]; edge=new Edge[this.nbEdges]; startingEdge=new int[this.nbNode][]; int[] curNbVoisins=new int[this.nbNode]; int vx,vy,x,y,v,curEdge=0,i1,i2; //Create nodes node[0]=new Node();node[0].index=0; node[1]=new Node();node[1].index=1; for(x=0;x<w;x++){ for(y=0;y<h;y++){ i1=x*h+y+2; node[i1]=new Node(); node[i1].index=i1; } } //Create array of starting Edges startingEdge[0]=new int[this.nbNode-2]; startingEdge[1]=new int[this.nbNode-2]; if((w==1)||(h==1)){ // Cas h==1 or w==1 for(x=0;x<w;x++){ for(y=0;y<h;y++){ if(x*(x+1-w)==0 && y*(y+1-h)==0){//Coins startingEdge[(x*h+y+2)]=new int[1+1]; }else if(x*(x+1-w)==0 || y*(y+1-h)==0){//Edges startingEdge[(x*h+y+2)]=new int[2+1]; } } } }else{ for(x=0;x<w;x++){ for(y=0;y<h;y++){ if(x*(x+1-w)==0 && y*(y+1-h)==0){//Coins startingEdge[(x*h+y+2)]=new int[3+2]; }else if(x*(x+1-w)==0 || y*(y+1-h)==0){//Edges startingEdge[(x*h+y+2)]=new int[5+2]; }else{//Milieu startingEdge[(x*h+y+2)]=new int[8+2]; } } } } // T-links : Sink Edges for(x=0;x<w;x++){ for(y=0;y<h;y++){ i1=x*h+y+2;i2=1; edge[curEdge]=new Edge(); edge[curEdge].initial_vertex=i1; edge[curEdge].terminal_vertex=i2; edge[curEdge].invEdgeIndex=curEdge+1; startingEdge[i1][curNbVoisins[i1]++]=curEdge; curEdge++; edge[curEdge]=new Edge(); edge[curEdge].initial_vertex=i2; edge[curEdge].terminal_vertex=i1; edge[curEdge].invEdgeIndex=curEdge-1; startingEdge[i2][(x*h+y)]=curEdge; curEdge++; } } // N-Links for(x=0;x<w;x++){ for(y=0;y<h;y++){ i1=x*h+y+2; for(v=0;v<voisinsEdgeACreer.length;v++){ vx=x+voisinsEdgeACreer[v][0]; vy=y+voisinsEdgeACreer[v][1]; if(vx<0 || vx>=w || vy<0 || vy>=h) continue; i2=vx*h+vy+2; edge[curEdge]=new Edge(); edge[curEdge].initial_vertex=i1; edge[curEdge].terminal_vertex=i2; edge[curEdge].invEdgeIndex=curEdge+1; startingEdge[i1][curNbVoisins[i1]++]=curEdge; curEdge++; edge[curEdge]=new Edge(); edge[curEdge].initial_vertex=i2; edge[curEdge].terminal_vertex=i1; edge[curEdge].invEdgeIndex=curEdge-1; startingEdge[i2][curNbVoisins[i2]++]=curEdge; curEdge++; } } } // T-links : Source Edges for(x=0;x<w;x++){ for(y=0;y<h;y++){ i1=0;i2=x*h+y+2; edge[curEdge]=new Edge(); edge[curEdge].initial_vertex=i1; edge[curEdge].terminal_vertex=i2; edge[curEdge].invEdgeIndex=curEdge+1; startingEdge[i1][(x*h+y)]=curEdge; curEdge++; edge[curEdge]=new Edge(); edge[curEdge].initial_vertex=i2; edge[curEdge].terminal_vertex=i1; edge[curEdge].invEdgeIndex=curEdge-1; startingEdge[i2][curNbVoisins[i2]++]=curEdge; //TODO:Cannot return to source curEdge++; } } if(curEdge!=nbEdges) System.out.println("Pb creation edges"); } /** * retrieve the edge between (x1,y1) and (x2,y2) * @param x1 * @param y1 * @param x2 * @param y2 * @return the matching edge if exists, null otherwise */ private Edge getEdge(int x1,int y1,int x2,int y2){ int i1=x1*h+y1+2,i2=x2*h+y2+2; for(int v=0;v<startingEdge[i1].length;v++){ if(edge[startingEdge[i1][v]].terminal_vertex==i2) return edge[startingEdge[i1][v]]; } return null; } /** * set the edge between (x1,y1) and (x2,y2) to the capacity w * @param x1 * @param y1 * @param x2 * @param y2 * @param w capacity */ public void setInternWeight(int x1,int y1,int x2,int y2,double w){ int i1=x1*h+y1+2,i2=x2*h+y2+2; for(int v=0;v<startingEdge[i1].length;v++){ if(edge[startingEdge[i1][v]].terminal_vertex==i2){ edge[startingEdge[i1][v]].capacity=w; edge[edge[startingEdge[i1][v]].invEdgeIndex].capacity=w; } } } /** * set the edge between Source and (x1,y1) to the capacity w * @param x1 * @param y1 * @param w capacity */ public void setSourceWeight(int x1,int y1,double w){ int i1=0,i2=x1*h+y1; edge[startingEdge[i1][i2]].capacity=w; edge[edge[startingEdge[i1][i2]].invEdgeIndex].capacity=w; } /** * set the edge between Sink and (x1,y1) to the capacity w * @param x1 * @param y1 * @param w capacity */ public void setSinkWeight(int x1,int y1,double w){ int i1=1,i2=x1*h+y1; edge[startingEdge[i1][i2]].capacity=w; edge[edge[startingEdge[i1][i2]].invEdgeIndex].capacity=w; } /** * set the edge between Source and (x1,y1) to the capacity wSource * set the edge between Sink and (x1,y1) to the capacity wSink * @param x1 * @param y1 * @param wSource capacity to the source * @param wSink capacity to the sink */ public void setTWeight(int x1,int y1,double wSource,double wSink){ int i2=x1*h+y1; edge[startingEdge[0][i2]].capacity=wSource; edge[edge[startingEdge[0][i2]].invEdgeIndex].capacity=-wSource; edge[startingEdge[1][i2]].capacity=-wSink; edge[edge[startingEdge[1][i2]].invEdgeIndex].capacity=wSink; } /** * return the part of the graph where (x1,y1) is * @param x1 * @param y1 * @return 0 if connected to Source, 1 if connected to Sink * 2 if isolated */ public int linkedTo(int x1,int y1){ int i2=x1*h+y1; if (edge[startingEdge[0][i2]].capacity-edge[startingEdge[0][i2]].flow>eps) return 0; else if(edge[edge[startingEdge[1][i2]].invEdgeIndex].capacity-edge[edge[startingEdge[1][i2]].invEdgeIndex].flow>eps){ return 1; }else{ return 2; } } /** * Calculate current Flow * @return current Flow */ public double getFlow(){ double f=0;int x,y; for(x=0;x<w;x++){ for(y=0;y<h;y++){ f+=edge[edge[startingEdge[1][(x*h+y)]].invEdgeIndex].flow; } } return f; } /** * Reset the flow in the graph */ private void resetFlow(){ for(int i=0;i<edge.length;i++) edge[i].flow=0; } /** * Based on Boykov & Kolmogorov Implementation * By Bastien Jacquet see "An experimental Comparison of Min-Cut/Max-Flow Algorithms for Energy Minimization in Vision" Boykov & Kolmogorov [2004] */ LinkedList<Integer> orphan;boolean[] isInS;LinkedList<Integer> active;boolean[] isInA; public void doCut(){ resetFlow(); isInS=new boolean[nbNode];isInS[0]=true; active=new LinkedList<Integer>();active.add(0);isInA=new boolean[nbNode];isInA[0]=true; orphan=new LinkedList<Integer>(); for(int i=0;i<node.length;i++)node[i].prevEdgeIndex=-1; while(true){ int lastEdge=growthStage(); if(lastEdge==-1) return; augmentationStage(lastEdge); adoptionStage(); } } /** * Phase 1 : growth Stage * Build the entire tree by setting prevEdgeIndex for each node * @return the last edge index of the path to Sink, -1 if no path */ private int growthStage(){ //if(debug) System.out.println("growthStage osize:"+orphan.size()); if (isInS[1]) return node[1].prevEdgeIndex; int curNodeIndex,curStartingEdgeIndex;Edge curEdge; while (!active.isEmpty()){ curNodeIndex=active.peek(); //Speed up we just flag an active node for deletion if(!isInA[curNodeIndex]){active.poll();continue;} for(curStartingEdgeIndex=0 ;curStartingEdgeIndex < startingEdge[curNodeIndex].length ; curStartingEdgeIndex++){ curEdge=edge[startingEdge[curNodeIndex][curStartingEdgeIndex]]; if(curEdge.capacity-curEdge.flow<=eps) continue; if(!isInS[curEdge.terminal_vertex]){ //if is in T active.add(curEdge.terminal_vertex);isInA[curEdge.terminal_vertex]=true; isInS[curEdge.terminal_vertex]=true; node[curEdge.terminal_vertex].prevEdgeIndex=startingEdge[curNodeIndex][curStartingEdgeIndex]; } if(curEdge.terminal_vertex==1) { return startingEdge[curNodeIndex][curStartingEdgeIndex]; } } active.poll();isInA[curNodeIndex]=false; } return -1; } /** * Phase 2 : augmentation Stage * saturate the path starting with the edge lastEdgeIndex * @param lastEdgeIndex */ private void augmentationStage(int lastEdgeIndex){ double bottleNeckCap; try { bottleNeckCap = edge[lastEdgeIndex].capacity-edge[lastEdgeIndex].flow; for(int curNodeIndex=edge[lastEdgeIndex].initial_vertex;curNodeIndex!=0;curNodeIndex=edge[node[curNodeIndex].prevEdgeIndex].initial_vertex){ if(bottleNeckCap>edge[node[curNodeIndex].prevEdgeIndex].capacity-edge[node[curNodeIndex].prevEdgeIndex].flow){ bottleNeckCap=edge[node[curNodeIndex].prevEdgeIndex].capacity-edge[node[curNodeIndex].prevEdgeIndex].flow; } } } catch (Exception e) { //TODO : This should never happens e.printStackTrace(); //if(node[edge[lastEdgeIndex].initial_vertex].prevEdgeIndex==-1) isInS=new boolean[nbNode];isInS[0]=true; active=new LinkedList<Integer>();active.add(0);isInA=new boolean[nbNode];isInA[0]=true; orphan=new LinkedList<Integer>(); return; } int prevEdgeIndex=-1; for(int curEdgeIndex=lastEdgeIndex;curEdgeIndex!=-1;curEdgeIndex=prevEdgeIndex){ edge[curEdgeIndex].flow+=bottleNeckCap; edge[edge[curEdgeIndex].invEdgeIndex].flow-=bottleNeckCap; prevEdgeIndex=node[edge[curEdgeIndex].initial_vertex].prevEdgeIndex; if(edge[curEdgeIndex].capacity-edge[curEdgeIndex].flow<=eps){ node[edge[curEdgeIndex].terminal_vertex].prevEdgeIndex=-1; orphan.addFirst(edge[curEdgeIndex].terminal_vertex); } } } private int getRootOf(int nodeIndex){ int curRootNodeIndex=nodeIndex; while(node[curRootNodeIndex].prevEdgeIndex>0){ curRootNodeIndex=edge[node[curRootNodeIndex].prevEdgeIndex].initial_vertex; } return curRootNodeIndex; } /** * Phase 3 : adoption Stage * Repair the search tree by processing orphans */ private void adoptionStage(){ int curNodeIndex,curStartingEdgeIndex;Edge curEdge; while (!orphan.isEmpty()){ curNodeIndex=orphan.poll(); boolean hasFindParent=false; //searching parent for(curStartingEdgeIndex=0 ;!hasFindParent && curStartingEdgeIndex < startingEdge[curNodeIndex].length ; curStartingEdgeIndex++){ //For each incoming edge curEdge=edge[edge[startingEdge[curNodeIndex][curStartingEdgeIndex]].invEdgeIndex]; if(curEdge.capacity-curEdge.flow<=eps) continue; if(!isInS[curEdge.initial_vertex])continue; int curRootNodeIndex=curEdge.initial_vertex; while(node[curRootNodeIndex].prevEdgeIndex>0){ curRootNodeIndex=edge[node[curRootNodeIndex].prevEdgeIndex].initial_vertex; } if(curRootNodeIndex!=0)continue; hasFindParent=true; node[curNodeIndex].prevEdgeIndex=edge[startingEdge[curNodeIndex][curStartingEdgeIndex]].invEdgeIndex; break; } // no parents possible if(!hasFindParent){ isInS[curNodeIndex]=false; //Speed up we just flag an active node for deletion isInA[curNodeIndex]=false; for(curStartingEdgeIndex=0 ;curStartingEdgeIndex < startingEdge[curNodeIndex].length ; curStartingEdgeIndex++){ curEdge=edge[startingEdge[curNodeIndex][curStartingEdgeIndex]]; //Children becomes orphans if(node[curEdge.terminal_vertex].prevEdgeIndex==startingEdge[curNodeIndex][curStartingEdgeIndex]){ node[curEdge.terminal_vertex].prevEdgeIndex=-1; orphan.addLast(curEdge.terminal_vertex); } if(!isInS[curEdge.terminal_vertex]) continue; curEdge=edge[curEdge.invEdgeIndex]; //Add in active if not already here if(curEdge.capacity-curEdge.flow>eps && !isInA[curEdge.initial_vertex]) { active.add(curEdge.initial_vertex); isInA[curEdge.initial_vertex]=true; } } } } } }