diff --git a/jgrapht-core/src/main/java/org/jgrapht/GraphTests.java b/jgrapht-core/src/main/java/org/jgrapht/GraphTests.java index 447bc1094c9..b2c124a9be0 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/GraphTests.java +++ b/jgrapht-core/src/main/java/org/jgrapht/GraphTests.java @@ -22,6 +22,7 @@ import org.jgrapht.alg.connectivity.KosarajuStrongConnectivityInspector; import org.jgrapht.alg.cycle.ChordalityInspector; import org.jgrapht.alg.cycle.HierholzerEulerianCycle; +import org.jgrapht.alg.intervalgraph.*; import java.util.*; import java.util.stream.Collectors; @@ -495,6 +496,24 @@ public static boolean isChordal(Graph graph){ Objects.requireNonNull(graph, GRAPH_CANNOT_BE_NULL); return new ChordalityInspector<>(graph).isChordal(); } + + /** + * Tests whether a graph is an interval graph. + * Interval graphs are a familiy of graphs, which can be represented as intersections of intervals. + * The vertices are intervals on the number line and two vertices are connected if and only if the intervals of + * these vertices intersect each other. + * + * @param graph the input graph + * @param the graph vertex type + * @param the graph edge type + * @return true if the graph is an interval graph, false otherwise + * @see IntervalGraphRecognizer#isIntervalGraph() + * + */ + public static boolean isIntervalGraph(Graph graph) { + Objects.requireNonNull(graph, GRAPH_CANNOT_BE_NULL); + return new IntervalGraphRecognizer<>(graph).isIntervalGraph(); + } /** * Tests whether an undirected graph meets Ore's condition to be Hamiltonian. diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/color/DecompositionTreeColour.java b/jgrapht-core/src/main/java/org/jgrapht/alg/color/DecompositionTreeColour.java new file mode 100644 index 00000000000..1661c3e3eaf --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/color/DecompositionTreeColour.java @@ -0,0 +1,115 @@ +package org.jgrapht.alg.color; + +import java.util.Collections; +import org.jgrapht.Graph; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import org.jgrapht.alg.interfaces.VertexColoringAlgorithm; + +/** + * Colouring of decomposition trees (currently done for interval graphs). This + * algorithm iterates over lists of vertices and assigns the smallest colour to + * each of the vertices such that the no vertex in the same list has the same + * colour. + * + * @author Suchanda Bhattacharyya (dia007) + * + * @param + * The type of graph vertex + * @param + * The type of graph edge + */ + +public class DecompositionTreeColour implements VertexColoringAlgorithm { + + /** + * The input graph + */ + private Graph graph; + /** + * The map of the vertices in the input graph to it's interval sets + */ + private Map> decompositionMap; + + /** + * + * @param graph the input graph + * @param decompositionMap the input decomposition map + */ + public DecompositionTreeColour(Graph graph, Map> decompositionMap) { + this.graph = Objects.requireNonNull(graph, "Graph cannot be null"); + this.decompositionMap = Objects.requireNonNull(decompositionMap, "there must be some decomposition present"); + } + + /** + * Getting the ordering for the vertices + * + * @return the ordering of the vertices + */ + + protected Iterable getVertexOrdering() { + return (Iterable) graph.vertexSet(); + + } + + /* + * (non-Javadoc) + * + * @see org.jgrapht.alg.interfaces.VertexColoringAlgorithm#getColoring() + */ + @Override + public Coloring getColoring() { + + Map asssignedColors = new HashMap<>(); + Set used = new HashSet<>(); + Set free = new HashSet<>(); + + // self loops not allowed, repetitions of inner vertices not allowed + + for (Integer vertex : getVertexOrdering()) { + + // find the intervals corresponding to the vertex + // need to sort the vertices + List intervalSet = decompositionMap.get(vertex).stream().sorted().collect(Collectors.toList()); + + for (V innerVertex : intervalSet) { + // first need to iterate over each innerVertex in the outerVertex to check that + // if there is any vertex with an already assigned colour + if (asssignedColors.containsKey(innerVertex)) { + used.add(asssignedColors.get(innerVertex)); + + } else { + // these are the vertices without any assigned colours + free.add(innerVertex); + + } + } + + // here we assign colours to the free vertices + + for (V freeVertex : free) { + int colourCandidate = 0; + while (used.contains(colourCandidate)) { + colourCandidate++; + } + + asssignedColors.put(freeVertex, colourCandidate); + + used.add(colourCandidate); + + } + free.clear(); + used.clear(); + + } + + int maxColourAssigned = Collections.max(asssignedColors.values()); + + return new ColoringImpl<>(asssignedColors, maxColourAssigned + 1); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/decomposition/IntervalGraphNiceDecompositionBuilder.java b/jgrapht-core/src/main/java/org/jgrapht/alg/decomposition/IntervalGraphNiceDecompositionBuilder.java new file mode 100644 index 00000000000..718688c2a26 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/decomposition/IntervalGraphNiceDecompositionBuilder.java @@ -0,0 +1,207 @@ +package org.jgrapht.alg.decomposition; + +import java.util.*; +import org.jgrapht.*; +import org.jgrapht.alg.intervalgraph.IntervalGraphRecognizer; +import org.jgrapht.intervalgraph.*; +import org.jgrapht.intervalgraph.interval.*; + +/** + * Class for calculating the nice Tree Decomposition for interval graphs + * + * @param the value type of the intervals of the interval graph + * @param the type of the nodes of the input graph + * + * @author Ira Justus Fesefeldt (PhoenixIra) + * @since Mai 14, 2018 + */ +public class IntervalGraphNiceDecompositionBuilder, V> extends NiceDecompositionBuilder +{ + // input to the algorithm, list of sorted intervals + private List> startSort, endSort; + private Map, V> intervalToVertexMap; + private Map> vertexToIntervalMap; + + // helper attributes + private Integer currentVertex = null; + + /** + * Private constructor for the factory methods, which changes the inputs + * + * @param sortedByStartPoint the intervals sorted after the starting points + * @param sortedByEndPoint the intervals sorted after the ending points + * @param intervalToVertexMap maps intervals to the vertices of the graph (may be the same) + */ + private IntervalGraphNiceDecompositionBuilder( + List> sortedByStartPoint, List> sortedByEndPoint, + Map, V> intervalToVertexMap, Map> vertexToIntervalMap) + { + super(); + + this.startSort = sortedByStartPoint; + this.endSort = sortedByEndPoint; + this.intervalToVertexMap = intervalToVertexMap; + this.vertexToIntervalMap = vertexToIntervalMap; + + computeNiceDecomposition(); + } + + /** + * Factory method for creating a nice tree decomposition for interval graphs. This factory + * method uses general graphs and the IntervalGraphRecognizer to generate a list of intervals + * sorted by starting and ending points. The complexity of this method depends on the sorting + * algorithm in IntervalGraphRecognizer (probably quasi linear in the number of intervals) + * + * @param graph the graph which should transformed to an interval graph and then into a + * corresponding nice tree decomposition + * @param the vertex type of the graph + * @param the edge type of the graph + * @return the algorithm for the computation of the nice tree decomposition, returns null if + * graph was no interval graph + * @see IntervalGraphRecognizer + */ + public static IntervalGraphNiceDecompositionBuilder create(Graph graph) + { + IntervalGraphRecognizer recog = new IntervalGraphRecognizer<>(graph); + + HashMap> vertexToIntegerMap = + new HashMap<>(recog.getVertexToIntervalMap().size()); + + Map> vertexToIntervalVertexMap = recog.getVertexToIntervalMap(); + for (V key : vertexToIntervalVertexMap.keySet()) + vertexToIntegerMap.put(key, vertexToIntervalVertexMap.get(key).getInterval()); + + if (recog.isIntervalGraph()) + return new IntervalGraphNiceDecompositionBuilder( + recog.getIntervalsSortedByStartingPoint(), + recog.getIntervalsSortedByEndingPoint(), recog.getIntervalToVertexMap(), + vertexToIntegerMap); + else + return null; + } + + /** + * Factory method for creating a nice tree decomposition for interval graphs. This factory + * method extracts the intervals from the interval graph and uses them as an input for the + * computation. The complexity of this method depends on the sorting algorithm of ArrayList + * (probably O(|V| log(|V|)) + * + * @param intervalGraph the input for which a nice tree decomposition should be computed + * @param the IntervalVertex Type of the interval graph + * @param the edge type of the interval graph + * @param the vertex type of the graph + * @param the value of the intervals + * @return the algorithm for the computation of the nice tree decomposition + * @see ArrayList#sort(Comparator) + */ + public static , E, VertexType, + T extends Comparable> IntervalGraphNiceDecompositionBuilder create( + IntervalGraph intervalGraph) + { + Set vertexSet = intervalGraph.vertexSet(); + List> intervals = new ArrayList>(vertexSet.size()); + Map, V> intervalToVertexMap = new HashMap<>(vertexSet.size()); + Map> vertexToIntervalMap = new HashMap<>(vertexSet.size()); + for (V iv : vertexSet) { + intervals.add(iv.getInterval()); + intervalToVertexMap.put(iv.getInterval(), iv); + vertexToIntervalMap.put(iv, iv.getInterval()); + } + ArrayList> startSort = new ArrayList<>(intervals); + ArrayList> endSort = new ArrayList<>(intervals); + startSort.sort(Interval. getStartingComparator()); + endSort.sort(Interval. getEndingComparator()); + return new IntervalGraphNiceDecompositionBuilder( + startSort, endSort, intervalToVertexMap, vertexToIntervalMap); + } + + /** + * Factory method for creating a nice tree decomposition for interval graphs. This factory + * method needs to lists of intervals, the first sorted after starting points, the second after + * ending points The complexity of this method is linear in the number of intervals + * + * @param sortedByStartPoint a list of all intervals sorted by the starting point + * @param sortedByEndPoint a list of all intervals sorted by the ending point + * @param the value of the intervals + * @return the algorithm for the computation of the nice tree decomposition + */ + public static > IntervalGraphNiceDecompositionBuilder> create( + List> sortedByStartPoint, List> sortedByEndPoint) + { + HashMap, Interval> identity = new HashMap<>(sortedByStartPoint.size()); + for (Interval interval : sortedByStartPoint) + identity.put(interval, interval); + return new IntervalGraphNiceDecompositionBuilder>( + new ArrayList>(sortedByStartPoint), + new ArrayList>(sortedByEndPoint), identity, identity); + } + + /** + * Factory method for creating a nice tree decomposition for interval graphs. This factory + * method needs to lists of intervals, which then is sorted by ArrayList.sort(). The complexity + * of this method depends on the sorting Algorithm of ArrayList (probably O(|List| log(|List|)) + * + * @param intervals the (unsorted) list of all intervals + * @param the values of the intervals + * @return the algorithm for the computation of the nice tree decomposition + * @see ArrayList#sort(Comparator) + */ + public static > IntervalGraphNiceDecompositionBuilder> create( + List> intervals) + { + ArrayList> startSort = new ArrayList<>(intervals); + ArrayList> endSort = new ArrayList<>(intervals); + startSort.sort(Interval. getStartingComparator()); + endSort.sort(Interval. getEndingComparator()); + return create(startSort, endSort); + } + + /** + * Main method for computing the nice tree decomposition + */ + private void computeNiceDecomposition() + { + + // create all objects and set new root + currentVertex = getRoot(); + + int endIndex = 0; + + // as long as intervals remain + for (Interval current : startSort) { + // first forget until you need to introduce new nodes + while (endSort.get(endIndex).getEnd().compareTo(current.getStart()) < 0) { + V forgetElement = intervalToVertexMap.get(endSort.get(endIndex)); + currentVertex = addForget(forgetElement, currentVertex); + endIndex++; + } + V introduceElement = intervalToVertexMap.get(current); + currentVertex = addIntroduce(introduceElement, currentVertex); + } + // add the last forget nodes + while (endIndex < endSort.size()) { + V forgetElement = intervalToVertexMap.get(endSort.get(endIndex++)); + currentVertex = addForget(forgetElement, currentVertex); + } + } + + /** + * getter for interval to vertex Map + * + * @return a map that maps intervals to vertices + */ + public Map, V> getIntervalToVertexMap() + { + return intervalToVertexMap; + } + + /** + * getter for vertex to interval map + * + * @return a map that maps vertices to intervals + */ + public Map> getVertexToIntervalMap() + { + return vertexToIntervalMap; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/decomposition/NiceDecompositionBuilder.java b/jgrapht-core/src/main/java/org/jgrapht/alg/decomposition/NiceDecompositionBuilder.java new file mode 100644 index 00000000000..8ba47a265ca --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/decomposition/NiceDecompositionBuilder.java @@ -0,0 +1,200 @@ +package org.jgrapht.alg.decomposition; + +import java.util.*; + +import org.jgrapht.*; +import org.jgrapht.alg.util.*; +import org.jgrapht.graph.*; + +/** + * A builder for nice tree decompositions. + * A tree decomposition of a graph G is a tree T and a map b:V(T) → Set<V(G)>, + * which satisfies the properties: + *
  • for every edge e in E(G), there is a node t in V(T) with e is a subset of b(v)
  • + *
  • for all vertices v in V(G) the set {t ∈ V(T) | v ∈ b(t)} is non-empty and connected in T
+ * + * A nice tree decomposition is a special tree decomposition, which satisfies the properties: + *
  • for root r ∈ V(T) and leaf l ∈ V(T): b(r)=b(t)=∅
  • + *
  • every non-leaf node t ∈ V(T) is of one of the following three types: + *
    • introduce node: t has exactly one child d and b(t) = b(d) ∪ w for some w ∈ V(G)
    • + *
    • forget node: t has exactly one child d and b(t) ∪ w = b(d) for some w ∈ V(G)\b(t)
    • + *
    • join node: t has exactly two child d_1, d_2 and b(t)=b(d_1)=b(d_2)
+ * + * @author Ira Justus Fesefeldt (PhoenixIra) + * + * @param the vertices of G + */ +abstract public class NiceDecompositionBuilder +{ + + // resulting decomposition + private Graph decomposition; + + // map from decomposition nodes to the interval sets + private Map> decompositionMap; + + // the root of the tree + private Integer root; + + // next integer for vertex generation + private Integer nextInteger; + + /** + * constructor for all methods used in the abstract method + */ + protected NiceDecompositionBuilder() + { + // creating objects + decomposition = new DefaultDirectedGraph(DefaultEdge.class); + decompositionMap = new HashMap>(); + + // create root + root = 0; + nextInteger = 1; + decompositionMap.put(root, new HashSet()); + decomposition.addVertex(root); + } + + /** + * getter for the next free Integer + * + * @return unused integer + */ + private Integer getNextInteger() + { + return nextInteger++; + } + + /** + * Method for adding a new join node + * + * @param toJoin which nodes should get a join node + * @return the new children of the join node, first element has no children, second element has + * the children of toJoin + */ + protected Pair addJoin(Integer toJoin) + { + Set vertexSet = null; + + // new + Integer vertexChildLeft = getNextInteger(); + decomposition.addVertex(vertexChildLeft); + vertexSet = new HashSet(decompositionMap.get(toJoin)); + decompositionMap.put(vertexChildLeft, vertexSet); + + // new current root + Integer vertexChildRight = getNextInteger(); + decomposition.addVertex(vertexChildRight); + vertexSet = new HashSet(decompositionMap.get(toJoin)); + decompositionMap.put(vertexChildRight, vertexSet); + + // redirect all edges to new parent (should be just one!) + for (Integer successor : Graphs.successorListOf(decomposition, toJoin)) { + decomposition.removeEdge(toJoin, successor); + decomposition.addEdge(vertexChildRight, successor); + } + // make children of parent vertex + decomposition.addEdge(toJoin, vertexChildLeft); + decomposition.addEdge(toJoin, vertexChildRight); + + return new Pair(vertexChildLeft, vertexChildRight); + } + + /** + * Method for adding introducing nodes. It is only usable if the vertex currentVertex is a leaf. + * It then adds the new introducing node as the child of currentVertex + * + * @param introducingElement the element, which is introduced + * @param currentVertex the vertex this element is introduced to + * @return the next vertex + */ + protected Integer addIntroduce(V introducingElement, Integer currentVertex) + { + Set nextVertexSet = new HashSet<>(decompositionMap.get(currentVertex)); + nextVertexSet.add(introducingElement); + Integer nextVertex = getNextInteger(); + decomposition.addVertex(nextVertex); + decomposition.addEdge(currentVertex, nextVertex); + decompositionMap.put(nextVertex, nextVertexSet); + + return nextVertex; + } + + /** + * method for adding forget nodes. It is only usable if the vertex currentVertex is a leaf. + * It then adds the new forget node as the child of currentVertex + * + * @param forgettingElement the element, which is forgotten + * @param currentVertex the vertex this element is forgotten + * @return the next vertex + */ + protected Integer addForget(V forgettingElement, Integer currentVertex) + { + Set nextVertexSet = new HashSet<>(decompositionMap.get(currentVertex)); + nextVertexSet.remove(forgettingElement); + Integer nextVertex = getNextInteger(); + decomposition.addVertex(nextVertex); + decomposition.addEdge(currentVertex, nextVertex); + decompositionMap.put(nextVertex, nextVertexSet); + + return nextVertex; + } + + /** + * Adds to all current leaves in the decomposition forget/introduce nodes until only empty sets are leafes + */ + protected void leafClosure() + { + Set vertices = new HashSet(decomposition.vertexSet()); + // make leave nodes + for (Integer leaf : vertices) { + // leaf is not a leaf + if (Graphs.vertexHasSuccessors(decomposition, leaf)) + continue; + + // otherwise add forget until empty set + Set vertexSet = decompositionMap.get(leaf); + Integer current = leaf; + for (V forget : vertexSet) { + current = addForget(forget, current); + } + } + } + + /** + * Getter for the decomposition as an unmodifiable, directed graph + * + * @return the computed decomposition + */ + public Graph getDecomposition() + { + return new AsUnmodifiableGraph<>(decomposition); + } + + /** + * Getter for an unmodifiable map from integer nodes of the decomposition to the intervals of the interval + * graph + * + * @return a nodes to interval map + */ + public Map> getMap() + { + return Collections.unmodifiableMap(decompositionMap); + } + + /** + * Getter for the root of the decomposition + * + * @return a set of roots + */ + public Integer getRoot() + { + return root; + } + + @Override + public String toString() + { + return getDecomposition() + "\n " + getMap(); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/decomposition/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/decomposition/package-info.java new file mode 100644 index 00000000000..3e43e3d84ee --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/decomposition/package-info.java @@ -0,0 +1,6 @@ + +/** + * Tree Decomposition related algorithms + * + */ +package org.jgrapht.alg.decomposition; diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/intervalgraph/IntervalGraphRecognizer.java b/jgrapht-core/src/main/java/org/jgrapht/alg/intervalgraph/IntervalGraphRecognizer.java new file mode 100644 index 00000000000..add26c3a486 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/intervalgraph/IntervalGraphRecognizer.java @@ -0,0 +1,388 @@ +package org.jgrapht.alg.intervalgraph; + +import static org.jgrapht.alg.intervalgraph.LexBreadthFirstSearch.*; + +import java.lang.reflect.Array; +import java.util.*; + +import org.jgrapht.*; +import org.jgrapht.intervalgraph.interval.*; + +/** + * A recognizer for interval graphs. + * + * An interval graph is a intersection graph of a set of intervals on the line, i.e. they contain a + * vertex for each interval and two vertices are connected if the corresponding intervals have a + * nonempty intersection. + * + * The recognizer uses the algorithm described in https://webdocs.cs.ualberta.ca/~stewart/Pubs/IntervalSIAM.pdf + * (The LBFS Structure and Recognition of Interval Graphs. SIAM J. Discrete Math.. 23. 1905-1953. + * 10.1137/S0895480100373455.) by Derek Corneil, Stephan Olariu and Lorna Stewart based on + * multiple lexicographical breadth-first search (LBFS) sweeps. + * + * For this recognizer to work correctly the graph must not be modified during iteration. + * + * + * @param the graph vertex type. + * @param the graph edge type. + * @author Team J + * @since April 2018 + */ +public final class IntervalGraphRecognizer +{ + + /** + * Stores whether or not the graph is an interval graph. + */ + private boolean isIntervalGraph; + + /** + * Stores the computed interval graph representation (or null if no such representation + * exists) of the graph. + */ + private ArrayList> intervalsSortedByStartingPoint, + intervalsSortedByEndingPoint; + private Map, V> intervalToVertexMap; + private Map> vertexToIntervalMap; + + /** + * Creates (and runs) a new interval graph recognizer for the given graph. + * + * @param graph the graph to be tested. + */ + @SuppressWarnings({ "unchecked" }) + public IntervalGraphRecognizer(Graph graph) + { + this.isIntervalGraph = isIntervalGraph(graph); + } + + /** + * check if the graph is an interval graph + * + * @return + */ + private boolean isIntervalGraph(Graph graph) + { + + // An empty graph is an interval graph. + if (graph.vertexSet().isEmpty()) { + this.intervalsSortedByStartingPoint = new ArrayList<>(); + return true; + } + + // Step 1 - LBFS from an arbitrary vertex + // Input - random vertex r + // Output - the result of current sweep alpha, further last vertex a visited by current + // sweep + HashMap sweepAlpha = + lexBreadthFirstSearch(graph, randomElementOf(graph.vertexSet())); + + // Step 2 - LBFS+ from the last vertex of the previous sweep + // Input - the result of previous sweep alpha, vertex a + // Output - the result of current sweep beta, further last vertex b visited by current sweep + HashMap sweepBeta = lexBreadthFirstSearchPlus(graph, sweepAlpha); + + // Step 3 - LBFS+ from the last vertex of the previous sweep + // Input - the result of previous sweep beta, vertex b + // Output - the result of current sweep gamma, further last vertex c visited by current + // sweep + HashMap sweepGamma = lexBreadthFirstSearchPlus(graph, sweepBeta); + + // Step 4 - LBFS+ from the last vertex of the previous sweep + // Input - the result of previous sweep gamma, vertex c + // Output - the result of current sweep delta, further last vertex d visited by current + // sweep + HashMap sweepDelta = lexBreadthFirstSearchPlus(graph, sweepGamma); + + // Step 5 - LBFS+ from the last vertex of the previous sweep + // Input - the result of previous sweep delta, vertex d + // Output - the result of current sweep epsilon, further last vertex e visited by current + // sweep + HashMap sweepEpsilon = lexBreadthFirstSearchPlus(graph, sweepDelta); + + // Step 6 - LBFS* with the resulting sweeps + // Input - the result of sweep gamma and sweep epsilon + // Output - the result of current sweep zeta + HashMap sweepZeta = lexBreadthFirstSearchStar(graph, sweepDelta, sweepEpsilon); + + // if sweepZeta is umbrella-free, then the graph is interval. + // otherwise, the graph is not interval + + if (isIOrdering(sweepZeta, graph)) { + + HashMap neighborIndex = new HashMap<>(); + for (V vertex : graph.vertexSet()) { + int maxNeighbor = 0; + + List neighbors = Graphs.neighborListOf(graph, vertex); + neighbors.add(vertex); + + for (V neighbor : neighbors) { + maxNeighbor = Math.max(maxNeighbor, sweepZeta.get(neighbor)); + } + + neighborIndex.put(vertex, maxNeighbor); + } + + Interval[] intervals = + (Interval[]) Array.newInstance(Interval.class, graph.vertexSet().size()); + this.intervalsSortedByStartingPoint = new ArrayList<>(graph.vertexSet().size()); + + // Initialize the vertex map. Because we know the number of vertices we can make sure + // the hashmap does not need to rehash by setting the capacity to the number of vertices + // divided by the default load factor of 0.75. + this.intervalToVertexMap = + new HashMap<>((int) Math.ceil(graph.vertexSet().size() / 0.75)); + this.vertexToIntervalMap = + new HashMap<>((int) Math.ceil(graph.vertexSet().size() / 0.75)); + + // Compute intervals and store them associated by their starting point ... + for (V vertex : graph.vertexSet()) { + Interval vertexInterval = + new Interval<>(sweepZeta.get(vertex), neighborIndex.get(vertex)); + + intervals[sweepZeta.get(vertex)] = vertexInterval; + + this.intervalToVertexMap.put(vertexInterval, vertex); + this.vertexToIntervalMap.put(vertex, IntervalVertex.of(vertex, vertexInterval)); + } + + // ... and produce a list sorted by the starting points for an efficient construction of + // the graph + for (int i = 0; i < graph.vertexSet().size(); i++) { + this.intervalsSortedByStartingPoint.add(intervals[i]); + } + + return true; + } else { + return false; + } + } + + /** + * Calculates if the given sweep is an I-Ordering (according to the Graph graph) in linear time. + * + * @param + * + * @param sweep the order we want to check if its an I-Order + * @param graph the graph we want to check if its an I-Order + * @return true, if sweep is an I-Order according to graph + */ + @SuppressWarnings({ "unchecked" }) + private static boolean isIOrdering(HashMap sweep, Graph graph) + { + // Compute inverse sweep map to quickly find vertices at given indices + V[] inverseSweep = (V[]) new Object[graph.vertexSet().size()]; + + for (V vertex : graph.vertexSet()) { + int index = sweep.get(vertex); + inverseSweep[index] = vertex; + } + // Compute maximal neighbors w.r.t. sweep ordering for every vertex + HashMap maxNeighbors = new HashMap<>(graph.vertexSet().size()); + + for (V vertex : graph.vertexSet()) { + List neighbors = Graphs.neighborListOf(graph, vertex); + V maxNeighbor = vertex; + + for (V neighbor : neighbors) { + if (sweep.get(neighbor) > sweep.get(maxNeighbor)) { + maxNeighbor = neighbor; + } + } + + maxNeighbors.put(vertex, maxNeighbor); + } + + // Check if every vertex is connected to all vertices between itself and its maximal neighbor + for (V vertex : graph.vertexSet()) { + int index = sweep.get(vertex); + int maxIndex = sweep.get(maxNeighbors.get(vertex)); + + for (int i = index; i < maxIndex; i++) { + if (vertex != inverseSweep[i] && !graph.containsEdge(vertex, inverseSweep[i])) { + // Found missing edge + return false; + } + } + } + + // No missing edge found + return true; + } + + /** + * return a random element of the given set + * + * @param set + * @param the generic type representing vertices + * @return + */ + private static V randomElementOf(Set set) + { + if (set == null) { + throw new IllegalArgumentException("Set parameter cannot be null."); + } + + int index = new Random().nextInt(set.size()); + Iterator iterator = set.iterator(); + for (int i = 0; i < index; i++) { + iterator.next(); + } + return iterator.next(); + } + + /** + * Returns whether or not the graph is an interval graph. + * + * @return true if the graph is an interval graph, otherwise false. + */ + public boolean isIntervalGraph() + { + return isIntervalGraph; + } + + /** + * Returns the list of all intervals sorted by starting point, or null, if the graph was not an + * interval graph. + * + * @return The list of all intervals sorted by starting point, or null, if the graph was not an + * interval graph. + */ + public ArrayList> getIntervalsSortedByStartingPoint() + { + return this.intervalsSortedByStartingPoint; + } + + /** + * Implementation of radix Sort for integers on Intervals after the ending point + * + * @param list list of intervals to sort + * @return a new sorted list of the intervals sorted after the ending point + */ + private ArrayList> radixSortInteger(List> list) + { + if(list == null) + throw new IllegalArgumentException("List parameter cannot be null."); + + if(list.isEmpty()) + return new ArrayList>(); + + ArrayList> positiveList = new ArrayList>(list.size()); + ArrayList> negativeList = new ArrayList>(list.size()); + for (Interval interval : list) { + if (interval.getEnd() < 0) + negativeList.add(interval); + else + positiveList.add(interval); + } + + positiveList = radixSortNatural(positiveList); + negativeList = radixSortNatural(negativeList); + negativeList.addAll(positiveList); + return negativeList; + } + + /** + * Implementation of radix Sort for natural numbers on Intervals after the ending point + * + * @param list list of intervals to sort + * @return a new sorted list of the intervals sorted after the ending point + */ + private ArrayList> radixSortNatural(List> list) + { + if(list == null) + throw new IllegalArgumentException("List parameter cannot be null."); + + if(list.isEmpty()) + return new ArrayList>(); + + ArrayList> intervals = new ArrayList>(list); + ArrayList> intervalsTmp = + new ArrayList>(intervals.size()); + + // init + for (int i = 0; i < intervals.size(); i++) + intervalsTmp.add(null); + + Interval max = + Collections.max(intervals, Interval. getEndingComparator()); + int power = 1; + // every digit + while (max.getEnd() / power > 0) { + int[] buckets = new int[10]; + + // count all numbers with digit at position exponent + for (int i = 0; i < intervals.size(); i++) { + int digit = Math.abs((intervals.get(i).getEnd() / power) % 10); + buckets[digit]++; + } + + // compute position of digits + for (int i = 1; i < 10; i++) { + buckets[i] += buckets[i - 1]; + } + + // sort after digit in intervalsTmp + for (int i = intervals.size() - 1; i >= 0; i--) { + int digit = Math.abs((intervals.get(i).getEnd() / power) % 10); + int position = buckets[digit] - 1; + buckets[digit] = position; + intervalsTmp.set(position, intervals.get(i)); + } + + // swap both + ArrayList> tmp = intervals; + intervals = intervalsTmp; + intervalsTmp = tmp; + + power *= 10; + } + return intervals; + } + + /** + * Returns the list of all intervals sorted by ending point, or null, if the graph was not an + * interval graph. + * The List will be created with radix sort from getIntervalsSortedByStartingPoint as soon as + * this method is called. + * + * @return The list of all intervals sorted by ending point, or null, if the graph was not an + * interval graph. + */ + public ArrayList> getIntervalsSortedByEndingPoint() + { + // + if (this.intervalsSortedByStartingPoint == null) + return null; + if (this.intervalsSortedByEndingPoint == null) + this.intervalsSortedByEndingPoint = + radixSortInteger(this.intervalsSortedByStartingPoint); + return this.intervalsSortedByEndingPoint; + } + + /** + * Returns a mapping of the constructed intervals to the vertices of the original graph, or + * null, if the graph was not an interval graph. + * + * @return A mapping of the constructed intervals to the vertices of the original graph, or + * null, if the graph was not an interval graph. + */ + public Map, V> getIntervalToVertexMap() + { + return this.intervalToVertexMap; + } + + /** + * Returns a mapping of the vertices of the original graph to the constructed intervals, or + * null, if the graph was not an interval graph. + * + * @return A mapping of the vertices of the original graph to the constructed intervals, or + * null, if the graph was not an interval graph. + */ + public Map> getVertexToIntervalMap() + { + return this.vertexToIntervalMap; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/intervalgraph/LexBreadthFirstSearch.java b/jgrapht-core/src/main/java/org/jgrapht/alg/intervalgraph/LexBreadthFirstSearch.java new file mode 100644 index 00000000000..3d1f5257f3e --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/intervalgraph/LexBreadthFirstSearch.java @@ -0,0 +1,120 @@ +package org.jgrapht.alg.intervalgraph; + +import org.jgrapht.*; +import org.jgrapht.traverse.*; + +import java.util.*; + +/** A class that is used to perform the BFS algorithms used to detect interval graphs. + * @param The vertex type + * @param The edge type + */ +class LexBreadthFirstSearch +{ + + /** + * Performs a lexicographical BFS starting at {@code startingVertex}. + * + * @param The vertex type + * @param The edge type + * @param graph the graph we want to perform LBFS on + * @param startingVertex the starting vertex of the LBFS + * @return an array of vertices representing the order in which the vertices were found + */ + static HashMap lexBreadthFirstSearch(Graph graph, V startingVertex) + { + HashMap result = new HashMap<>(graph.vertexSet().size()); + LexBreadthFirstIterator lbfIterator = new LexBreadthFirstIterator<>(graph, startingVertex); + + for(int i = 0; i < graph.vertexSet().size(); i++) { + result.put(lbfIterator.next(), i); + } + + return result; + } + + /** + * Performs LBFS+ starting at {@code startingVertex} using the previous ordering {@code prevOrdering}. + * + * @param graph the graph we want to perform LBFS on + * @param priority the priority of vertices for tiebreaking + * @param The vertex type + * @param The edge type + * @return an array of vertices representing the order in which the vertices were found + */ + + static HashMap lexBreadthFirstSearchPlus(Graph graph, HashMap priority) + { + HashMap result = new HashMap<>(graph.vertexSet().size()); + LexBreadthFirstIterator lbfIterator = new LexBreadthFirstIterator<>(graph, priority); + + for(int i = 0; i < graph.vertexSet().size(); i++) { + result.put(lbfIterator.next(), i); + } + + return result; + } + + /** + * Performs LBFS* starting at {@code startingVertex} using two previous orderings {@code prevOrdering1} and {@code prevOrdering2}. + * + * @param graph the graph we want to perform LBFS on + * @param priorityA the first priority of vertices for tiebreaking + * @param priorityB the second priority of vertices for tiebreaking + * @param The vertex type + * @param The edge type + * @return an array of vertices representing the order in which the vertices were found + */ + + static HashMap lexBreadthFirstSearchStar(Graph graph, HashMap priorityA, HashMap priorityB) + { + HashMap neighborIndexA = new HashMap<>(); + HashMap neighborIndexB = new HashMap<>(); + + HashMap> ASets = new HashMap<>(); + HashMap> BSets = new HashMap<>(); + + for(V vertex : graph.vertexSet()) { + // Compute indexA, indexB + int maxNeighborA = 0; + int maxNeighborB = 0; + + List neighbors = Graphs.neighborListOf(graph, vertex); + neighbors.add(vertex); + + for(V neighbor : neighbors) { + maxNeighborA = Math.max(maxNeighborA, priorityA.get(neighbor)); + maxNeighborB = Math.max(maxNeighborB, priorityB.get(neighbor)); + } + + neighborIndexA.put(vertex, maxNeighborA); + neighborIndexB.put(vertex, maxNeighborB); + } + + for(V vertex : graph.vertexSet()) { + HashSet Av = new HashSet<>(); + HashSet Bv = new HashSet<>(); + + for(V neighbor : Graphs.neighborListOf(graph, vertex)) { + if(priorityA.get(neighbor) < priorityA.get(vertex) && neighborIndexA.get(neighbor) > priorityA.get(vertex)) { + Av.add(neighbor); + } + + if(priorityB.get(neighbor) < priorityB.get(vertex) && neighborIndexB.get(neighbor) > priorityB.get(vertex)) { + Bv.add(neighbor); + } + } + + ASets.put(vertex, Av); + BSets.put(vertex, Bv); + } + + HashMap result = new HashMap<>(graph.vertexSet().size()); + LexBreadthFirstIterator lbfIterator = new LexBreadthFirstIterator<>(graph, priorityA, priorityB, neighborIndexA, neighborIndexB, ASets, BSets); + + for(int i = 0; i < graph.vertexSet().size(); i++) { + result.put(lbfIterator.next(), i); + } + return result; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/alg/intervalgraph/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/alg/intervalgraph/package-info.java new file mode 100644 index 00000000000..fcfba4693ee --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/alg/intervalgraph/package-info.java @@ -0,0 +1,6 @@ + +/** + * Interval graph related algorithms. + * + */ +package org.jgrapht.alg.intervalgraph; diff --git a/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/IntervalSpecifics.java b/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/IntervalSpecifics.java new file mode 100644 index 00000000000..2c69f6e20a8 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/IntervalSpecifics.java @@ -0,0 +1,280 @@ +package org.jgrapht.graph.specifics; + +import org.jgrapht.graph.EdgeSetFactory; +import org.jgrapht.intervalgraph.IntervalGraph; +import org.jgrapht.intervalgraph.IntervalGraphVertexContainerInterface; +import org.jgrapht.intervalgraph.IntervalGraphVertexContainer; +import org.jgrapht.intervalgraph.interval.IntervalVertexInterface; +import org.jgrapht.util.ArrayUnenforcedSet; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * Implementation of IntervalSpecifics. + * This class implements necessary methods for IntervalGraph. + * + * @param the interval vertex type + * @param the edge type + * @param the internal vertex type + * @param The underlying type for intervals + * + * @author Christoph Grüne (christophgruene) + * @since Apr 26, 2018 + */ +public class IntervalSpecifics, E, VertexType, T extends Comparable> implements Specifics, Serializable { + + private static final long serialVersionUID = 1112673663745687843L; + + private IntervalGraph intervalGraph; + private IntervalGraphVertexContainerInterface intervalGraphVertexContainerInterface; + private EdgeSetFactory edgeSetFactory; + + /** + * Constructs new interval specifics. + * + * @param intervalGraph the graph for which these specifics are for + */ + public IntervalSpecifics(IntervalGraph intervalGraph) { + this.intervalGraph = intervalGraph; + this.intervalGraphVertexContainerInterface = new IntervalGraphVertexContainer<>(); + this.edgeSetFactory = new ArrayUnenforcedSetEdgeSetFactory<>(); + } + + /** + * Constructs new interval specifics. + * + * @param intervalGraph the graph for which these specifics are for + * @param vertices The vertices of the graph + */ + public IntervalSpecifics(IntervalGraph intervalGraph, ArrayList vertices) { + + this.intervalGraph = intervalGraph; + this.edgeSetFactory = new ArrayUnenforcedSetEdgeSetFactory<>(); + + ArrayList> undirectedEdgeContainers = new ArrayList<>(vertices.size()); + for(int i = 0; i < vertices.size(); ++i) { + undirectedEdgeContainers.add(i, new UndirectedEdgeContainer<>(edgeSetFactory, vertices.get(i))); + } + + this.intervalGraphVertexContainerInterface = new IntervalGraphVertexContainer<>(vertices, undirectedEdgeContainers); + } + + /** + * {@inheritDoc} + */ + @Override + public void addVertex(V vertex) + { + getEdgeContainer(vertex); + } + + /** + * Gets the overlapping vertices for a given vertex + * + * @param vertex The input vertex + * + * @return A list of vertices that overlap the input vertex + */ + public List getOverlappingIntervalVertices(V vertex) { + return intervalGraphVertexContainerInterface.getOverlappingIntervalVertices(vertex); + } + + /** + * Removes a vertex + * + * @param v The vertex that should be removed + */ + public void removeVertex(V v) { + intervalGraphVertexContainerInterface.remove(v); + } + + /** + * {@inheritDoc} + */ + @Override + public Set getVertexSet() + { + return intervalGraphVertexContainerInterface.getVertexSet(); + } + + /** + * {@inheritDoc} + */ + @Override + public Set getAllEdges(V sourceVertex, V targetVertex) + { + Set edges = null; + + if (intervalGraph.containsVertex(sourceVertex) + && intervalGraph.containsVertex(targetVertex)) + { + edges = new ArrayUnenforcedSet<>(); + + for (E e : getEdgeContainer(sourceVertex).vertexEdges) { + boolean equal = isEqualsStraightOrInverted(sourceVertex, targetVertex, e); + + if (equal) { + edges.add(e); + } + } + } + + return edges; + } + + /** + * {@inheritDoc} + */ + @Override + public E getEdge(V sourceVertex, V targetVertex) + { + if (intervalGraph.containsVertex(sourceVertex) + && intervalGraph.containsVertex(targetVertex)) + { + + for (E e : getEdgeContainer(sourceVertex).vertexEdges) { + boolean equal = isEqualsStraightOrInverted(sourceVertex, targetVertex, e); + + if (equal) { + return e; + } + } + } + + return null; + } + + private boolean isEqualsStraightOrInverted(Object sourceVertex, Object targetVertex, E e) + { + boolean equalStraight = sourceVertex.equals(intervalGraph.getEdgeSource(e)) + && targetVertex.equals(intervalGraph.getEdgeTarget(e)); + + boolean equalInverted = sourceVertex.equals(intervalGraph.getEdgeTarget(e)) + && targetVertex.equals(intervalGraph.getEdgeSource(e)); + return equalStraight || equalInverted; + } + + /** + * {@inheritDoc} + */ + @Override + public void addEdgeToTouchingVertices(E e) + { + V source = intervalGraph.getEdgeSource(e); + V target = intervalGraph.getEdgeTarget(e); + + getEdgeContainer(source).addEdge(e); + + if (!source.equals(target)) { + getEdgeContainer(target).addEdge(e); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int degreeOf(V vertex) + { + if (intervalGraph.isAllowingLoops()) { // then we must count, and add loops twice + int degree = 0; + Set edges = getEdgeContainer(vertex).vertexEdges; + + for (E e : edges) { + if (intervalGraph.getEdgeSource(e).equals(intervalGraph.getEdgeTarget(e))) { + degree += 2; + } else { + degree += 1; + } + } + + return degree; + } else { + return getEdgeContainer(vertex).edgeCount(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public Set edgesOf(V vertex) + { + return getEdgeContainer(vertex).getUnmodifiableVertexEdges(); + } + + /** + * {@inheritDoc} + */ + @Override + public int inDegreeOf(V vertex) + { + return degreeOf(vertex); + } + + /** + * {@inheritDoc} + */ + @Override + public Set incomingEdgesOf(V vertex) + { + return getEdgeContainer(vertex).getUnmodifiableVertexEdges(); + } + + /** + * {@inheritDoc} + */ + @Override + public int outDegreeOf(V vertex) + { + return degreeOf(vertex); + } + + /** + * {@inheritDoc} + */ + @Override + public Set outgoingEdgesOf(V vertex) + { + return getEdgeContainer(vertex).getUnmodifiableVertexEdges(); + } + + /** + * {@inheritDoc} + */ + @Override + public void removeEdgeFromTouchingVertices(E e) + { + V source = intervalGraph.getEdgeSource(e); + V target = intervalGraph.getEdgeTarget(e); + + getEdgeContainer(source).removeEdge(e); + + if (!source.equals(target)) { + getEdgeContainer(target).removeEdge(e); + } + } + + /** + * Get the edge container for a specified vertex. + * + * @param vertex a vertex in this graph + * + * @return an edge container + */ + protected UndirectedEdgeContainer getEdgeContainer(V vertex) + { + UndirectedEdgeContainer ec = intervalGraphVertexContainerInterface.get(vertex); + + if (ec == null) { + ec = new UndirectedEdgeContainer<>(edgeSetFactory, vertex); + intervalGraphVertexContainerInterface.put(vertex, ec); + } + + return ec; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/BinarySearchTree.java b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/BinarySearchTree.java new file mode 100644 index 00000000000..e1c7f18a587 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/BinarySearchTree.java @@ -0,0 +1,184 @@ +package org.jgrapht.intervalgraph; + +import java.util.NoSuchElementException; + +/** + * Interface for Binary Search Trees + * + * @param the key + * @param the value + * + * @author Christoph Grüne (christophgruene) + * @since Apr 18, 2018 + */ +public interface BinarySearchTree { + + /******************************************************************************************************************* + * Search Operations * + ******************************************************************************************************************/ + + /** + * Returns the value associated with the given key + * + * @param key the key + * @return the value associated with the given key. If the key is not in the tree, null is returned. + * @throws IllegalArgumentException if key is null + */ + V get(K key); + + /** + * Returns whether a key is contained in the tree + * + * @param key the key + * @return true if tree contains key, false otherwise + * @throws IllegalArgumentException if key is null + */ + boolean contains(K key); + + + /******************************************************************************************************************* + * Insertion Operations * + ******************************************************************************************************************/ + + /** + * Inserts the given (key, value) pair into the tree. If the tree contains already a symbol with the given key + * it overwrites the old value with the new. + * + * @param key the key + * @param val the value + * @throws IllegalArgumentException if key is null + */ + void insert(K key, V val); + + + /******************************************************************************************************************* + * Deletion Operations * + ******************************************************************************************************************/ + + /** + * Removes the specified key and its associated value from this tree + * + * @param key the key + * @throws IllegalArgumentException if key is null + */ + void delete(K key); + + /** + * Removes the smallest key and associated value from the tree. + * @throws NoSuchElementException if the tree is empty + */ + void deleteMin(); + + /** + * Removes the largest key and associated value from the tree. + * @throws NoSuchElementException if the tree is empty + */ + void deleteMax(); + + + /******************************************************************************************************************* + * Utility Operations * + ******************************************************************************************************************/ + + /** + * Returns the height of the BST. + * @return the height of the BST (a tree with 1 node has height 0) + */ + int height(); + + + /******************************************************************************************************************* + * Special Search Operations * + ******************************************************************************************************************/ + + /** + * Returns the smallest key in the tree. + * @return the smallest key in the tree + * @throws NoSuchElementException if the tree is empty + */ + K min(); + /** + * Returns the largest key in the tree. + * @return the largest key in the tree + * @throws NoSuchElementException if the tree is empty + */ + K max(); + + /** + * Returns the largest key in the tree less than or equal to key. + * + * @param key the key + * @return the largest key in the tree less than or equal to key + * @throws NoSuchElementException if there is no such key + * @throws IllegalArgumentException if key is null + */ + K floor(K key); + + /** + * Returns the smallest key in the tree greater than or equal to key. + * + * @param key the key + * @return the smallest key in the tree greater than or equal to key + * @throws NoSuchElementException if there is no such key + * @throws IllegalArgumentException if key is null + */ + K ceiling(K key); + + /** + * Return the key in the tree whose rank is k. + * This is the (k+1)st smallest key in the tree. + * + * @param k the position + * @return the key in the tree of rank k + * @throws IllegalArgumentException if k not in {0, ..., n-1} + */ + K select(int k); + + /** + * Return the number of keys in the tree strictly less than key. + * + * @param key the key + * @return the number of keys in the tree strictly less than key + * @throws IllegalArgumentException if key is null + */ + int rank(K key); + + + /******************************************************************************************************************* + * Range Search Operations * + ******************************************************************************************************************/ + + /** + * Returns all keys in the symbol table as an Iterable. + * To iterate over all of the keys in the symbol table named st, + * use the foreach notation: for (IntervalTreeNodeKey key : st.keys()). + * + * @return all keys in the symbol table as an Iterable + */ + Iterable keys(); + + /** + * Returns all keys in the symbol table in the given range, + * as an Iterable. + * + * @param min minimum endpoint + * @param max maximum endpoint + * @return all keys in the sybol table between min + * (inclusive) and max (inclusive) as an Iterable + * @throws IllegalArgumentException if either min or max + * is null + */ + Iterable keys(K min, K max); + + /** + * Returns the number of keys in the symbol table in the given range. + * + * @param min minimum endpoint + * @param max maximum endpoint + * @return the number of keys in the sybol table between min + * (inclusive) and max (inclusive) + * @throws IllegalArgumentException if either min or max + * is null + */ + int size(K min, K max); +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/CenteredIntervalTree.java b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/CenteredIntervalTree.java new file mode 100644 index 00000000000..e0dc0cc15e9 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/CenteredIntervalTree.java @@ -0,0 +1,122 @@ +package org.jgrapht.intervalgraph; + +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; + +import org.jgrapht.Graph; +import org.jgrapht.intervalgraph.interval.Interval; + +/** + * A centered interval tree that is used to efficiently store interval graphs + * @param The type of intervals stored + */ +class CenteredIntervalTree> implements IntervalGraphInterface { + private T centerPoint; + private CenteredIntervalTree leftTree; // contains all the intervals *completely* to the left of the center point + private CenteredIntervalTree rightTree; // contains all the intervals *completely* to the right of the center point + private List> intersectionsByStart = new LinkedList<>(); // intervals intersecting center point, sorted by start point + private List> intersectionsByEnd = new LinkedList<>(); // ..., sorted by end point + private boolean isEmpty = true; + + public CenteredIntervalTree(){} + + + public CenteredIntervalTree(List> intervals) { + initialize(intervals); + } + + private void initialize(List> intervals) { + if (intervals == null){ + throw new IllegalArgumentException(); + } + + if (intervals.isEmpty()) { + return; + } + isEmpty = false; + + Interval randomInterval = intervals.get(ThreadLocalRandom.current().nextInt(intervals.size())); + if (ThreadLocalRandom.current().nextBoolean()) { + this.centerPoint = randomInterval.getStart(); + } else { + this.centerPoint = randomInterval.getEnd(); + } + + List> leftIntervals = new LinkedList<>(); // containing the intervals completely to the left of the center point + List> rightIntervals = new LinkedList<>(); // ... to the right + List> intersectingIntervals = new LinkedList<>(); // intervals intersecting the center point + + for (Interval interval: intervals) { + if (interval.contains(centerPoint)) { + intersectingIntervals.add(interval); + } else if (interval.compareToPoint(centerPoint) < 0) { // is completely left to center point + leftIntervals.add(interval); + } else if (interval.compareToPoint(centerPoint) > 0) { // completely right + rightIntervals.add(interval); + } + } + + // sorting the intervals according to start/end point + intersectionsByStart = new LinkedList<>(intersectingIntervals); + intersectionsByStart.sort(Comparator.comparing(Interval::getStart)); + + intersectionsByEnd = intersectingIntervals; + intersectionsByEnd.sort(Collections.reverseOrder(Comparator.comparing(Interval::getStart))); + + // add children to the left and right + leftTree = new CenteredIntervalTree<>(); + leftTree.initialize(leftIntervals); + + rightTree = new CenteredIntervalTree<>(); + rightTree.initialize(rightIntervals); + } + + @Override + public Collection> intersections(T point) { + Set> result = new HashSet<>(); // TODO Be aware, that I dont know if using sets guarantees linear run time + intersections(point, result); + return result; + } + + @Override + public Collection> intersections(Interval queryInterval) { + throw new RuntimeException("Method not implemented."); + } + + private void intersections(T point, Set> result) { + if (isEmpty) { + return; + } + + if (point.equals(centerPoint)){ + result.addAll(intersectionsByEnd); // or intersectionsByStart + } else if (point.compareTo(centerPoint) < 0) { + // add all intervals with start point <= center point + for (Interval interval: intersectionsByStart) { + if (interval.getStart().compareTo(point) > 0) { + break; + } + result.add(interval); + } + + // recursion to child in tree + leftTree.intersections(point, result); + } else { // point > centerPoint + // add all intervals with end point >= center point + for (Interval interval: intersectionsByEnd) { + if (interval.getEnd().compareTo(point) < 0) { + break; + } + result.add(interval); + } + + // recursion to child in tree + rightTree.intersections(point, result); + } + } + + @Override + public Graph asGraph() { + throw new RuntimeException("Method not implemented."); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalGraph.java b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalGraph.java new file mode 100644 index 00000000000..ead7fe71845 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalGraph.java @@ -0,0 +1,659 @@ +package org.jgrapht.intervalgraph; + +import org.jgrapht.EdgeFactory; +import org.jgrapht.Graph; +import org.jgrapht.GraphType; +import org.jgrapht.Graphs; +import org.jgrapht.alg.intervalgraph.IntervalGraphRecognizer; +import org.jgrapht.alg.util.Pair; +import org.jgrapht.graph.*; +import org.jgrapht.graph.specifics.IntervalSpecifics; +import org.jgrapht.intervalgraph.interval.Interval; +import org.jgrapht.intervalgraph.interval.IntervalVertex; +import org.jgrapht.intervalgraph.interval.IntervalVertexInterface; +import org.jgrapht.util.TypeUtil; + +import java.io.Serializable; +import java.util.*; + +/** + * Implementation of an Interval Graph + * + * @param the vertex type + * @param the edge type + * @param the internal vertex type + * @param The underlying type for intervals + * + * @author Christoph Grüne (christophgruene) + * @since Apr 18, 2018 + */ +public class IntervalGraph, E, VertexType, T extends Comparable> extends AbstractGraph implements Graph, Cloneable, Serializable { + + private static final long serialVersionUID = 7835287075273098344L; + + private static final String INTERVAL_GRAPH_ADD_EDGE = "Intervals of nodes define edges in interval graphs and cannot be modified manually"; + private static final String GRAPH_SPECIFICS_MUST_NOT_BE_NULL = "Graph specifics must not be null"; + + private EdgeFactory edgeFactory; + private transient Set unmodifiableVertexSet = null; + + private IntervalSpecifics specifics; + private IntrusiveEdgesSpecifics intrusiveEdgesSpecifics; + + private boolean directed; + private boolean weighted; + private boolean allowingMultipleEdges; + private boolean allowingLoops; + + /** + * Construct a new graph. The graph can either be directed or undirected, depending on the + * specified edge factory. + * + * @param ef the edge factory of the new graph. + * @param weighted whether the graph is weighted, i.e. the edges support a weight attribute + * + * @throws NullPointerException if the specified edge factory is + * null. + */ + protected IntervalGraph( + EdgeFactory ef, boolean weighted) + { + Objects.requireNonNull(ef); + + this.edgeFactory = ef; + this.allowingLoops = false; + this.allowingMultipleEdges = false; + this.directed = false; + this.specifics = + Objects.requireNonNull(createSpecifics(directed), GRAPH_SPECIFICS_MUST_NOT_BE_NULL); + this.weighted = weighted; + this.intrusiveEdgesSpecifics = Objects.requireNonNull( + createIntrusiveEdgesSpecifics(weighted), GRAPH_SPECIFICS_MUST_NOT_BE_NULL); + } + + /** + * Construct a new graph with given vertices. The graph can either be directed or undirected, depending on the + * specified edge factory. + * + * @param vertices initial vertices + * @param ef the edge factory of the new graph. + * @param weighted whether the graph is weighted, i.e. the edges support a weight attribute + * + * @throws NullPointerException if the specified edge factory is + * null. + */ + protected IntervalGraph( + ArrayList vertices, EdgeFactory ef, boolean weighted) + { + + Objects.requireNonNull(ef); + + boolean isSorted = true; + V current = vertices.get(0); + for (int i = 1; i < vertices.size(); i++) { + V next = vertices.get(i); + if (current.getInterval().compareTo(next.getInterval()) > 0) { + isSorted = false; + break; + } + current = next; + } + + this.edgeFactory = ef; + this.allowingLoops = false; + this.allowingMultipleEdges = false; + this.directed = false; + + this.weighted = weighted; + this.intrusiveEdgesSpecifics = Objects.requireNonNull( + createIntrusiveEdgesSpecifics(weighted), GRAPH_SPECIFICS_MUST_NOT_BE_NULL); + + if(isSorted) { + this.specifics = Objects.requireNonNull(createSpecifics(directed, vertices), GRAPH_SPECIFICS_MUST_NOT_BE_NULL); + + ArrayList> edges = new ArrayList<>(vertices.size()); + + for(int i = 0; i < vertices.size(); ++i) { + edges.add(i, new LinkedList<>()); + for(int j = 0; j < i; ++j) { + if(vertices.get(j).getInterval().getEnd().compareTo(vertices.get(i).getInterval().getStart()) >= 0) { + edges.get(j).add(vertices.get(i)); + } + } + } + + for(int i = 0; i < vertices.size(); ++i) { + addIntervalEdges(vertices.get(i), edges.get(i)); + } + + } else { + this.specifics = Objects.requireNonNull(createSpecifics(directed), GRAPH_SPECIFICS_MUST_NOT_BE_NULL); + + for (V vertex : vertices) { + this.addVertex(vertex); + } + } + } + + /** + * Construct a new graph with given vertices. The graph can either be directed or undirected, depending on the + * specified edge factory. + * + * @param vertices initial vertices + * @param edges initial edges + * @param ef the edge factory of the new graph. + * @param directed if true the graph will be directed, otherwise undirected + * @param allowMultipleEdges whether to allow multiple (parallel) edges or not. + * @param allowLoops whether to allow edges that are self-loops or not. + * @param weighted whether the graph is weighted, i.e. the edges support a weight attribute + * + * @throws NullPointerException if the specified edge factory is + * null. + */ + private IntervalGraph(ArrayList vertices, Map, E> edges, EdgeFactory ef, + boolean directed, boolean allowMultipleEdges, boolean allowLoops, boolean weighted) { + + Objects.requireNonNull(ef); + + this.edgeFactory = ef; + this.allowingLoops = allowLoops; + this.allowingMultipleEdges = allowMultipleEdges; + this.directed = directed; + this.specifics = + Objects.requireNonNull(createSpecifics(directed, vertices), GRAPH_SPECIFICS_MUST_NOT_BE_NULL); + this.weighted = weighted; + this.intrusiveEdgesSpecifics = Objects.requireNonNull( + createIntrusiveEdgesSpecifics(weighted), GRAPH_SPECIFICS_MUST_NOT_BE_NULL); + + for(Pair v : edges.keySet()) { + addExistingIntervalEdges(v.getFirst(), v.getSecond(), edges.get(v)); + } + } + + private boolean addExistingIntervalEdges(V sourceVertex, V targetVertex, E edge) { + + assertVertexExist(sourceVertex); + assertVertexExist(targetVertex); + + if(intrusiveEdgesSpecifics.add(edge, sourceVertex, targetVertex)) { + specifics.addEdgeToTouchingVertices(edge); + } + + return true; + } + + /** + * returns interval graph representation if one exists, otherwise null + * + * @param graph the graph to check + * @param the edge type + * @param the internal vertex type + * @return interval graph representation if one exists, otherwise null + */ + public static + IntervalGraph, E, VertexType, Integer> asIntervalGraph(Graph graph) { + + IntervalGraphRecognizer intervalGraphRecognizer = new IntervalGraphRecognizer<>(graph); + + if(!intervalGraphRecognizer.isIntervalGraph()) { + return null; + } + + ArrayList> sortedIntervalList = intervalGraphRecognizer.getIntervalsSortedByStartingPoint(); + Map, VertexType> intervalVertexMap = intervalGraphRecognizer.getIntervalToVertexMap(); + Map> vertexIntervalMap = intervalGraphRecognizer.getVertexToIntervalMap(); + + ArrayList> vertices = new ArrayList<>(sortedIntervalList.size()); + + for(int i = 0; i < sortedIntervalList.size(); ++i) { + vertices.add(i, vertexIntervalMap.get(intervalVertexMap.get(sortedIntervalList.get(i)))); + } + + Map, IntervalVertexInterface>, E> edges = new LinkedHashMap<>(); + + for(IntervalVertexInterface vertex : vertices) { + for(Iterator it = graph.outgoingEdgesOf(vertex.getVertex()).iterator(); it.hasNext();) { + E edge = it.next(); + edges.put(Pair.of(vertex, vertexIntervalMap.get(graph.getEdgeTarget(edge))), edge); + } + } + + + return new IntervalGraph<>(vertices, edges, (sourceVertex, targetVertex) -> graph.getEdgeFactory().createEdge(sourceVertex.getVertex(), targetVertex.getVertex()), + graph.getType().isDirected(), graph.getType().isAllowingMultipleEdges(), + graph.getType().isAllowingSelfLoops(), graph.getType().isWeighted()); + } + + + /** + * {@inheritDoc} + */ + @Override + public Set getAllEdges(V sourceVertex, V targetVertex) + { + return specifics.getAllEdges(sourceVertex, targetVertex); + } + + /** + * Returns true if and only if self-loops are allowed in this graph. A self loop is + * an edge that its source and target vertices are the same. + * + * @return true if and only if graph loops are allowed. + */ + public boolean isAllowingLoops() + { + return allowingLoops; + } + + /** + * Returns true if and only if multiple (parallel) edges are allowed in this graph. The + * meaning of multiple edges is that there can be many edges going from vertex v1 to vertex v2. + * + * @return true if and only if multiple (parallel) edges are allowed. + */ + public boolean isAllowingMultipleEdges() + { + return allowingMultipleEdges; + } + + /** + * Returns true if and only if the graph supports edge weights. + * + * @return true if the graph supports edge weights, false otherwise. + */ + public boolean isWeighted() + { + return weighted; + } + + /** + * Returns true if the graph is directed, false if undirected. + * + * @return true if the graph is directed, false if undirected. + */ + public boolean isDirected() + { + return directed; + } + + /** + * {@inheritDoc} + */ + @Override + public E getEdge(V sourceVertex, V targetVertex) + { + return specifics.getEdge(sourceVertex, targetVertex); + } + + /** + * {@inheritDoc} + */ + @Override + public EdgeFactory getEdgeFactory() + { + return edgeFactory; + } + + /** + * This method an IllegalArgumentException in every case because you can not add edges to an interval graph manually. + * + * {@inheritDoc} + */ + @Override + public E addEdge(V sourceVertex, V targetVertex) + { + throw new IllegalArgumentException(INTERVAL_GRAPH_ADD_EDGE); + } + + /** + * This method an IllegalArgumentException in every case because you can not add edges to an interval graph manually. + * + * {@inheritDoc} + */ + @Override + public boolean addEdge(V sourceVertex, V targetVertex, E e) + { + throw new IllegalArgumentException(INTERVAL_GRAPH_ADD_EDGE); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean addVertex(V v) + { + if (v == null) { + throw new NullPointerException(); + } else if (containsVertex(v)) { + return false; + } else { + specifics.addVertex(v); + // add all edges between the new vertex and vertices with intersecting intervals + addIntervalEdges(v, specifics.getOverlappingIntervalVertices(v)); + return true; + } + } + + /** + * {@inheritDoc} + */ + @Override + public V getEdgeSource(E e) + { + return intrusiveEdgesSpecifics.getEdgeSource(e); + } + + /** + * {@inheritDoc} + */ + @Override + public V getEdgeTarget(E e) + { + return intrusiveEdgesSpecifics.getEdgeTarget(e); + } + + /** + * Returns a shallow copy of this graph instance. Neither edges nor vertices are cloned. + * + * @return a shallow copy of this graph. + * + * @throws RuntimeException in case the clone is not supported + * + * @see java.lang.Object#clone() + */ + @Override + public Object clone() + { + try { + IntervalGraph newGraph = TypeUtil.uncheckedCast(super.clone()); + + newGraph.edgeFactory = this.edgeFactory; + newGraph.unmodifiableVertexSet = null; + + // NOTE: it's important for this to happen in an object + // method so that the new inner class instance gets associated with + // the right outer class instance + newGraph.specifics = newGraph.createSpecifics(this.directed); + newGraph.intrusiveEdgesSpecifics = + newGraph.createIntrusiveEdgesSpecifics(this.weighted); + + Graphs.addGraph(newGraph, this); + + return newGraph; + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + throw new RuntimeException(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean containsEdge(E e) + { + return intrusiveEdgesSpecifics.containsEdge(e); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean containsVertex(V v) + { + return specifics.getVertexSet().contains(v); + } + + /** + * {@inheritDoc} + */ + @Override + public int degreeOf(V vertex) + { + assertVertexExist(vertex); + return specifics.degreeOf(vertex); + } + + /** + * {@inheritDoc} + */ + @Override + public Set edgeSet() + { + return intrusiveEdgesSpecifics.getEdgeSet(); + } + + /** + * {@inheritDoc} + */ + @Override + public Set edgesOf(V vertex) + { + assertVertexExist(vertex); + return specifics.edgesOf(vertex); + } + + /** + * {@inheritDoc} + */ + @Override + public int inDegreeOf(V vertex) + { + assertVertexExist(vertex); + return specifics.inDegreeOf(vertex); + } + + /** + * {@inheritDoc} + */ + @Override + public Set incomingEdgesOf(V vertex) + { + assertVertexExist(vertex); + return specifics.incomingEdgesOf(vertex); + } + + /** + * {@inheritDoc} + */ + @Override + public int outDegreeOf(V vertex) + { + assertVertexExist(vertex); + return specifics.outDegreeOf(vertex); + } + + /** + * {@inheritDoc} + */ + @Override + public Set outgoingEdgesOf(V vertex) + { + assertVertexExist(vertex); + return specifics.outgoingEdgesOf(vertex); + } + + /** + * This method an IllegalArgumentException in every case because you can not remove edges from an interval graph manually. + * + * {@inheritDoc} + */ + @Override + public E removeEdge(V sourceVertex, V targetVertex) + { + throw new IllegalArgumentException(INTERVAL_GRAPH_ADD_EDGE); + } + + /** + * This method an IllegalArgumentException in every case because you can not remove edges from an interval graph manually. + * + * {@inheritDoc} + */ + @Override + public boolean removeEdge(E e) + { + throw new IllegalArgumentException(INTERVAL_GRAPH_ADD_EDGE); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean removeVertex(V v) + { + if (containsVertex(v)) { + removeAllEdgesFromVertex(v);//remove all edges from the given vertex to delete it safely + + specifics.removeVertex(v);// remove the vertex itself + + return true; + } else { + return false; + } + } + + /** + * {@inheritDoc} + */ + @Override + public Set vertexSet() + { + if (unmodifiableVertexSet == null) { + unmodifiableVertexSet = Collections.unmodifiableSet(specifics.getVertexSet()); + } + + return unmodifiableVertexSet; + } + + /** + * {@inheritDoc} + */ + @Override + public double getEdgeWeight(E e) + { + if (e == null) { + throw new NullPointerException(); + } + return intrusiveEdgesSpecifics.getEdgeWeight(e); + } + + /** + * Set an edge weight. + * + * @param e the edge + * @param weight the weight + * @throws UnsupportedOperationException if the graph is not weighted + */ + @Override + public void setEdgeWeight(E e, double weight) + { + if (e == null) { + throw new NullPointerException(); + } + intrusiveEdgesSpecifics.setEdgeWeight(e, weight); + } + + /** + * {@inheritDoc} + */ + @Override + public GraphType getType() + { + if (directed) { + return new DefaultGraphType.Builder() + .directed().weighted(weighted).allowMultipleEdges(allowingMultipleEdges) + .allowSelfLoops(allowingLoops).build(); + } else { + return new DefaultGraphType.Builder() + .undirected().weighted(weighted).allowMultipleEdges(allowingMultipleEdges) + .allowSelfLoops(allowingLoops).build(); + } + } + + /** + * Create the specifics for this graph. Subclasses can override this method in order to adjust + * the specifics and thus the space-time tradeoffs of the graph implementation. + * + * @param directed if true the specifics should adjust the behavior to a directed graph + * otherwise undirected + * @return the specifics used by this graph + */ + protected IntervalSpecifics createSpecifics(boolean directed) { + return new IntervalSpecifics<>(this); + } + + /** + * Create the specifics for this graph. Subclasses can override this method in order to adjust + * the specifics and thus the space-time tradeoffs of the graph implementation. + * + * @param directed if true the specifics should adjust the behavior to a directed graph + * otherwise undirected + * @param vertices The set of vertices + * @return the specifics used by this graph + */ + protected IntervalSpecifics createSpecifics(boolean directed, ArrayList vertices) { + return new IntervalSpecifics<>(this, vertices); + } + + /** + * Create the specifics for the edges set of the graph. + * + * @param weighted if true the specifics should support weighted edges + * @return the specifics used for the edge set of this graph + */ + protected IntrusiveEdgesSpecifics createIntrusiveEdgesSpecifics(boolean weighted) { + if (weighted) { + return new WeightedIntrusiveEdgesSpecifics<>(); + } else { + return new UniformIntrusiveEdgesSpecifics<>(); + } + } + + /** + * Adds edges between sourceVertex and every vertex from vertices. + * + * @param sourceVertex source vertex of all edges + * @param targetVertices target vertices of edges + */ + private boolean addIntervalEdges(V sourceVertex, Collection targetVertices) { + + assertVertexExist(sourceVertex); + + for(V targetVertex: targetVertices) { + assertVertexExist(targetVertex); + + if(!sourceVertex.equals(targetVertex)) { + E e = edgeFactory.createEdge(sourceVertex, targetVertex); + + if (intrusiveEdgesSpecifics.add(e, sourceVertex, targetVertex)) { + specifics.addEdgeToTouchingVertices(e); + } + } + } + return true; + } + + /** + * @see Graph#removeAllEdges(Collection) + */ + private boolean removeAllEdgesFromVertex(V vertex) { + Set touchingEdgesList = edgesOf(vertex); + + // cannot iterate over list - will cause + // ConcurrentModificationException + ArrayList edges = new ArrayList<>(touchingEdgesList); + + boolean modified = false; + + for (E e : edges) { + + if (containsEdge(e)) { + specifics.removeEdgeFromTouchingVertices(e); + intrusiveEdgesSpecifics.remove(e); + modified = true; + } + } + + return modified; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalGraphInterface.java b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalGraphInterface.java new file mode 100644 index 00000000000..1b021201a3b --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalGraphInterface.java @@ -0,0 +1,33 @@ +package org.jgrapht.intervalgraph; + +import org.jgrapht.Graph; +import org.jgrapht.intervalgraph.interval.Interval; + +import java.util.Collection; + +/** + * An interface for interval graph containers + * @param The underlying type for the intervals + */ +public interface IntervalGraphInterface> { + + /** + * Returns a collection of intersecting intervals + * @param queryPoint The query point + * @return A collection of intersecting intervals + */ + Collection> intersections(T queryPoint); + + /** + * Returns a collection of intersecting intervals + * @param queryInterval The query interval + * @return A collection of intersecting intervals + */ + Collection> intersections(Interval queryInterval); + + /** + * Returns a graph of the stored intervals + * @return A graph of the stored intervals + */ + Graph asGraph(); +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalGraphVertexContainer.java b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalGraphVertexContainer.java new file mode 100644 index 00000000000..4a23df7b3e5 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalGraphVertexContainer.java @@ -0,0 +1,131 @@ +package org.jgrapht.intervalgraph; + +import org.jgrapht.graph.specifics.UndirectedEdgeContainer; +import org.jgrapht.intervalgraph.interval.Interval; +import org.jgrapht.intervalgraph.interval.IntervalVertexInterface; + +import java.io.Serializable; +import java.util.*; + +/** + * Implementation of IntervalGraphVertexContainer + * + * @param the vertex type + * @param the edge type + * @param the internal vertex type + * @param the type of the interval + * + * @author Christoph Grüne (christophgruene) + * @since Apr 26, 2018 + */ +public class IntervalGraphVertexContainer, E, VertexType, T extends Comparable> implements IntervalGraphVertexContainerInterface, Serializable { + + private static final long serialVersionUID = 7768940080894764546L; + + /** + * intervalStructure maintains all intervals in order to get intersecting intervals efficiently. + */ + private IntervalStructureInterface intervalStructure; + /** + * vertexMap maintains an EdgeContainer for every Vertex + */ + private Map> vertexMap; + /** + * intervalMap maintains the assignment of every interval to vertex + */ + private Map, V> intervalMap; + + /** + * Constructs a vertex container for an interval graph with all necessary objects. + */ + public IntervalGraphVertexContainer() { + this.intervalStructure = new IntervalTreeStructure<>(); + this.vertexMap = new LinkedHashMap<>(); + this.intervalMap = new LinkedHashMap<>(); + } + + /** + * Constructs a vertex container for an interval graph with all necessary objects. + * + * @param vertices The set of vertices + * @param undirectedEdgeContainers The set of undirected edge containers + */ + public IntervalGraphVertexContainer(ArrayList vertices, ArrayList> undirectedEdgeContainers) { + this.vertexMap = new LinkedHashMap<>(); + this.intervalMap = new LinkedHashMap<>(); + + ArrayList> intervals = new ArrayList<>(vertices.size()); + + for(int i = 0; i < vertices.size(); ++i) { + intervals.add(i, vertices.get(i).getInterval()); + intervalMap.put(vertices.get(i).getInterval(), vertices.get(i)); + vertexMap.put(vertices.get(i), undirectedEdgeContainers.get(i)); + } + + this.intervalStructure = new IntervalTreeStructure<>(intervals); + } + + /** + * Returns the whole vertex set of the graph. + * + * @return all vertices of the graph in a set + */ + @Override + public Set getVertexSet() { + return vertexMap.keySet(); + } + + /** + * returns a list of all vertices with overlapping interval w.r.t v + * + * @param vertex the vertex with interval + */ + @Override + public List getOverlappingIntervalVertices(V vertex) { + List> intervalList = intervalStructure.overlapsWith(vertex.getInterval()); + List vertexList = new LinkedList<>(); + + for(Interval interval: intervalList) { + vertexList.add(intervalMap.get(interval)); + } + + return vertexList; + } + + /** + * Returns the edge container to the vertex + * + * @param vertex the vertex + * @return the edge container to the vertex + */ + @Override + public UndirectedEdgeContainer get(V vertex) { + return vertexMap.get(vertex); + } + + /** + * puts the given edge container to the data structure + * + * @param vertex the vertex + * @param ec the edge container + */ + @Override + public UndirectedEdgeContainer put(V vertex, UndirectedEdgeContainer ec) { + intervalStructure.add(vertex.getInterval()); + intervalMap.put(vertex.getInterval(), vertex); + return vertexMap.put(vertex, ec); + } + + /** + * Removes a vertex from the data structure if it is present. + * + * @param vertex the vertex to be removed + * @return true if this data structure contained the specified element + */ + @Override + public UndirectedEdgeContainer remove(V vertex) { + intervalStructure.remove(vertex.getInterval()); + intervalMap.remove(vertex.getInterval()); + return vertexMap.remove(vertex); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalGraphVertexContainerInterface.java b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalGraphVertexContainerInterface.java new file mode 100644 index 00000000000..ead1bcb12ab --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalGraphVertexContainerInterface.java @@ -0,0 +1,61 @@ +package org.jgrapht.intervalgraph; + +import org.jgrapht.graph.specifics.UndirectedEdgeContainer; +import org.jgrapht.intervalgraph.interval.IntervalVertexInterface; + +import java.util.List; +import java.util.Set; + +/** + * Interface of IntervalGraphVertexContainer + * + * @param the vertex type + * @param the edge type + * + * @author Christoph Grüne (christophgruene) + * @since Apr 26, 2018 + */ +public interface IntervalGraphVertexContainerInterface { + + /** + * Returns the whole vertex set of the graph. + * + * @return all vertices of the graph in a set + */ + Set getVertexSet(); + + /** + * returns a list of all vertices with overlapping interval w.r.t v + * + * @param v the vertex with interval + * + * @return A list of overlapping intervals + */ + List getOverlappingIntervalVertices(V v); + + /** + * Returns the edge container to the vertex + * + * @param vertex the vertex + * @return the edge container to the vertex + */ + UndirectedEdgeContainer get(V vertex); + + /** + * puts the given edge container to the data structure + * + * @param vertex the vertex + * @param ec the edge container + * + * @return An undirected edge container + */ + UndirectedEdgeContainer put(V vertex, UndirectedEdgeContainer ec); + + /** + * Removes a vertex from the data structure if it is present. + * + * @param vertex the vertex to be removed + * @return true if this data structure contained the specified element + */ + UndirectedEdgeContainer remove(V vertex); +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalStructureInterface.java b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalStructureInterface.java new file mode 100644 index 00000000000..cbfadc40632 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalStructureInterface.java @@ -0,0 +1,47 @@ +package org.jgrapht.intervalgraph; + +import org.jgrapht.intervalgraph.interval.Interval; + +import java.util.List; + +/** + * Interface of IntervalStructure + * This interface is used for an implementation of an efficient data structure to maintain intervals. + * + * @param the type of the interval + * + * @author Christoph Grüne (christophgruene) + * @since Apr 26, 2018 + */ +public interface IntervalStructureInterface> { + + /** + * Returns all intervals that overlap with the given interval + * + * @param interval the interval + * @return all intervals that overlap with the given interval + */ + List> overlapsWith(Interval interval); + + + /** + * Returns all intervals that overlap with the given point + * @param point the point + * @return all intervals that overlap with the given point + */ + List> overlapsWithPoint(T point); + + /** + * adds an interval to the interval tree + * + * @param interval the interval + */ + void add(Interval interval); + + /** + * removes an interval from the tree + * + * @param interval the interval + */ + void remove(Interval interval); +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalTreeInterface.java b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalTreeInterface.java new file mode 100644 index 00000000000..abb28650788 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalTreeInterface.java @@ -0,0 +1,38 @@ +package org.jgrapht.intervalgraph; + +import org.jgrapht.intervalgraph.interval.Interval; + +import java.util.List; + +/** + * Interface of IntervalTree + * This is the interface to an interval tree. It maintains intervals in order to find intersecting intervals and + * included points in O(log n + k), where n is the number of intervals and k the output size, + * i.e. the number of output intervals. + * + * @param The type of the interval + * @param The key for the search tree + * @param The node value type + * + * @author Christoph Grüne (christophgruene) + * @since Apr 26, 2018 + */ +public interface IntervalTreeInterface< T extends Comparable, K extends Comparable, NodeValue extends IntervalTreeNodeValue, T>> extends BinarySearchTree { + + /** + * Returns all intervals of all vertices that intersect with the given interval + * + * @param interval the interval + * @return all intersecting intervals of vertices in the graph + */ + List> overlapsWith(Interval interval); + + /** + * Returns all intervals of all vertices that include the given point + * + * @param point the point + * @return all including intervals of vertices in the graph + */ + List> overlapsWith(T point); + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalTreeNodeKey.java b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalTreeNodeKey.java new file mode 100644 index 00000000000..77b266e3414 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalTreeNodeKey.java @@ -0,0 +1,138 @@ +package org.jgrapht.intervalgraph; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.Objects; + +/** + * Implementation of IntervalTreeNodeKey + * This is a container class to store the necessary key data for the (augmented) interval tree in the nodes of the tree. + * + * @param the type of the interval + * + * @author Christoph Grüne (christophgruene) + * @since May 02, 2018 + */ +public class IntervalTreeNodeKey implements Comparable> { + + private T key; + private Comparator comparator; + + /** + * constructs an IntervalTreeNodeKey object + * + * @param key the content of the key + * @param comparator the comparator defining the order of the key + */ + public IntervalTreeNodeKey(T key, Comparator comparator) { + this.key = key; + this.comparator = comparator; + } + + /** + * Compares this object with the specified object for order. Returns a + * negative integer, zero, or a positive integer as this object is less + * than, equal to, or greater than the specified object. + * + *

The implementor must ensure + * {@code sgn(x.compareTo(y)) == -sgn(y.compareTo(x))} + * for all {@code x} and {@code y}. (This + * implies that {@code x.compareTo(y)} must throw an exception iff + * {@code y.compareTo(x)} throws an exception.) + * + *

The implementor must also ensure that the relation is transitive: + * {@code (x.compareTo(y) > 0 && y.compareTo(z) > 0)} implies + * {@code x.compareTo(z) > 0}. + * + *

Finally, the implementor must ensure that {@code x.compareTo(y)==0} + * implies that {@code sgn(x.compareTo(z)) == sgn(y.compareTo(z))}, for + * all {@code z}. + * + *

It is strongly recommended, but not strictly required that + * {@code (x.compareTo(y)==0) == (x.equals(y))}. Generally speaking, any + * class that implements the {@code Comparable} interface and violates + * this condition should clearly indicate this fact. The recommended + * language is "Note: this class has a natural ordering that is + * inconsistent with equals." + * + *

In the foregoing description, the notation + * {@code sgn(}expression{@code )} designates the mathematical + * signum function, which is defined to return one of {@code -1}, + * {@code 0}, or {@code 1} according to whether the value of + * expression is negative, zero, or positive, respectively. + * + * @param o the object to be compared. + * @return a negative integer, zero, or a positive integer as this object + * is less than, equal to, or greater than the specified object. + * @throws NullPointerException if the specified object is null + * @throws ClassCastException if the specified object's type prevents it + * from being compared to this object. + */ + @Override + public int compareTo(IntervalTreeNodeKey o) { + return comparator.compare(this.key, o.key); + } + + /** + * Indicates whether some other object is "equal to" this one. + *

+ * The {@code equals} method implements an equivalence relation + * on non-null object references: + *

    + *
  • It is reflexive: for any non-null reference value + * {@code x}, {@code x.equals(x)} should return + * {@code true}. + *
  • It is symmetric: for any non-null reference values + * {@code x} and {@code y}, {@code x.equals(y)} + * should return {@code true} if and only if + * {@code y.equals(x)} returns {@code true}. + *
  • It is transitive: for any non-null reference values + * {@code x}, {@code y}, and {@code z}, if + * {@code x.equals(y)} returns {@code true} and + * {@code y.equals(z)} returns {@code true}, then + * {@code x.equals(z)} should return {@code true}. + *
  • It is consistent: for any non-null reference values + * {@code x} and {@code y}, multiple invocations of + * {@code x.equals(y)} consistently return {@code true} + * or consistently return {@code false}, provided no + * information used in {@code equals} comparisons on the + * objects is modified. + *
  • For any non-null reference value {@code x}, + * {@code x.equals(null)} should return {@code false}. + *
+ *

+ * The {@code equals} method for class {@code Object} implements + * the most discriminating possible equivalence relation on objects; + * that is, for any non-null reference values {@code x} and + * {@code y}, this method returns {@code true} if and only + * if {@code x} and {@code y} refer to the same object + * ({@code x == y} has the value {@code true}). + *

+ * Note that it is generally necessary to override the {@code hashCode} + * method whenever this method is overridden, so as to maintain the + * general contract for the {@code hashCode} method, which states + * that equal objects must have equal hash codes. + * + * @param obj the reference object with which to compare. + * @return {@code true} if this object is the same as the obj + * argument; {@code false} otherwise. + * @see #hashCode() + * @see HashMap + */ + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + else if (!(obj instanceof IntervalTreeNodeKey)) + return false; + + @SuppressWarnings("unchecked") IntervalTreeNodeKey other = (IntervalTreeNodeKey) obj; + return Objects.equals(key, other.key) && Objects.equals(comparator, other.comparator); + } + + @Override + public int hashCode() { + return Objects.hash(key, comparator); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalTreeNodeValue.java b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalTreeNodeValue.java new file mode 100644 index 00000000000..3104a7b8299 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalTreeNodeValue.java @@ -0,0 +1,93 @@ +package org.jgrapht.intervalgraph; + +import org.jgrapht.intervalgraph.interval.Interval; + +import java.io.Serializable; +import java.util.Objects; + +/** + * Implementation of IntervalTreeNodeValue + * This is a container class to store the necessary data for the (augmented) interval tree in the nodes of the tree. + * + * @param the type of the interval + * @param The underlying type for the intervals + * + * @author Christoph Grüne (christophgruene) + * @since Apr 26, 2018 + */ +public class IntervalTreeNodeValue, T extends Comparable> implements Serializable { + + private static final long serialVersionUID = 1111005364785643338L; + + /** + * The interval + */ + private Interval interval; + + /** + * the greatest end point of all intervals in the subtree rooted at that node. + */ + private T highValue; + + /** + * Create a new pair + * + * @param interval the first element + */ + public IntervalTreeNodeValue(I interval) { + this.interval = interval; + this.highValue = interval.getEnd(); + } + + /** + * Get the first element of the pair + * + * @return the first element of the pair + */ + public Interval getInterval() + { + return interval; + } + + /** + * Get the second element of the pair + * + * @return the second element of the pair + */ + public T getHighValue() + { + return highValue; + } + + /** + * Sets the high value + * @param highValue The high value + */ + public void setHighValue(T highValue) { + this.highValue = highValue; + } + + @Override + public String toString() + { + return "(" + interval + "," + highValue + ")"; + } + + @Override + public boolean equals(Object o) + { + if (this == o) + return true; + else if (!(o instanceof IntervalTreeNodeValue)) + return false; + + @SuppressWarnings("unchecked") IntervalTreeNodeValue other = (IntervalTreeNodeValue) o; + return interval.equals(other.interval); + } + + @Override + public int hashCode() + { + return Objects.hash(interval, highValue); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalTreeStructure.java b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalTreeStructure.java new file mode 100644 index 00000000000..83b71fb49e3 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/IntervalTreeStructure.java @@ -0,0 +1,99 @@ +package org.jgrapht.intervalgraph; + +import org.jgrapht.intervalgraph.interval.Interval; + +import java.io.Serializable; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + * Implementation of IntervalTreeStructure + * This class realises the interval tree structure, which has a interval tree as maintaining object. + * + * @param the type of the interval + * + * @author Christoph Grüne (christophgruene) + * @since Apr 26, 2018 + */ +public class IntervalTreeStructure> implements IntervalStructureInterface, Serializable { + + private static final long serialVersionUID = 2834567756342332325L; + + private IntervalTreeInterface>, IntervalTreeNodeValue, T>> tree; + + /** + * Initializes a new instance of the class + */ + public IntervalTreeStructure() { + this.tree = new RedBlackIntervalTree<>(); + } + + /** + * Initializes a new instance of the class + * @param intervals The set of contained intervals + */ + public IntervalTreeStructure(ArrayList> intervals) { + ArrayList>> keys = new ArrayList<>(intervals.size()); + ArrayList, T>> values = new ArrayList<>(intervals.size()); + + for(int i = 0; i < intervals.size(); ++i) { + keys.add(i, new IntervalTreeNodeKey<>(intervals.get(i), getComparator())); + values.add(i, new IntervalTreeNodeValue<>(intervals.get(i))); + } + + this.tree = new RedBlackIntervalTree<>(keys, values); + } + + /** + * Returns all intervals that overlap with the given interval + * + * @param interval the interval + * @return all intervals that overlap with the given interval + */ + @Override + public List> overlapsWith(Interval interval) { + return tree.overlapsWith(interval); + } + + @Override + public List> overlapsWithPoint(T point) { + return tree.overlapsWith(point); + } + + /** + * adds an interval to the interval tree + * + * @param interval the interval + */ + @Override + public void add(Interval interval) { + tree.insert(new IntervalTreeNodeKey<>(interval, getComparator()), new IntervalTreeNodeValue<>(interval)); + } + + /** + * removes an interval from the tree + * + * @param interval the interval + */ + @Override + public void remove(Interval interval) { + tree.delete(new IntervalTreeNodeKey<>(interval, getComparator())); + } + + /** + * returns the comparator used to compare the keys in the interval tree + * + */ + private Comparator> getComparator() { + return (o1, o2) -> { + int startCompare = o1.getStart().compareTo(o2.getStart()); + if (startCompare != 0) { + return startCompare; + } else { + return o1.getEnd().compareTo(o2.getEnd()); + } + }; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/Node.java b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/Node.java new file mode 100644 index 00000000000..ff997225f54 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/Node.java @@ -0,0 +1,177 @@ +package org.jgrapht.intervalgraph; + +import java.io.Serializable; + +/** + * Implementation of Node + * This class implements the node for a BST. + * + * @param the key + * @param the value + * + * @author Christoph Grüne (christophgruene) + * @author Daniel Mock (danielmock) + * @since Apr 26, 2018 + */ +public class Node implements Serializable { + + private static final long serialVersionUID = 5674337686253743843L; + + /** + * the key of the node + */ + private K key; + /** + * the value of the node + */ + private V val; + /** + * node's corresponding left and right children + */ + private Node leftChild, rightChild; + /** + * the color of the node (important for usage in red black tree)(not necessary for all implementations) + */ + private boolean red; + /** + * the size of the sub tree rooted at this node (not necessary for all implementations) + */ + private int size; + + /** + * constructs a node object + * + * @param key the key of the node + * @param val the value of the node + * @param red the color of the node + * @param size the size of the sub tree rooted at the node + */ + public Node(K key, V val, boolean red, int size) { + this.key = key; + this.val = val; + this.red = red; + this.size = size; + } + + /** + * sets a new left child of the node + * + * @param leftChild the node that should be new left child + */ + protected void setLeftChild(Node leftChild) { + this.leftChild = leftChild; + } + + /** + * sets a new right child of the node + * + * @param rightChild the node that should be new right child + */ + protected void setRightChild(Node rightChild) { + this.rightChild = rightChild; + } + + /** + * sets the color for the node + * + * @param red red: true, black: false + */ + protected void setRed(boolean red) { + this.red = red; + } + + /** + * sets the color of the node to red + */ + protected void setRed() { + this.red = true; + } + + /** + * sets the color of the node to black + */ + protected void setBlack() { + this.red = false; + } + + /** + * sets a new size for the sub tree rooted at this node + * + * @param size the size of the sub tree rooted at this node + */ + protected void setSize(int size) { + this.size = size; + } + + /** + * returns whether node is red or black + * + * @return red:true, black:false + */ + protected boolean isRed() { + return red; + } + + /** + * Getter for key + * + * @return the key of the node + */ + protected K getKey() { + return key; + } + + /** + * Getter for val + * + * @return the value of the node + */ + protected V getVal() { + return val; + } + + /** + * Setter for val + * + * @param val the new value of the node + */ + public void setVal(V val) { + this.val = val; + } + + /** + * Getter for leftChild + * + * @return the left child of the node + */ + protected Node getLeftChild() { + return leftChild; + } + + /** + * Getter for rightChild + * + * @return the right child of the node + */ + protected Node getRightChild() { + return rightChild; + } + + /** + * Getter for size + * + * @return the size of the sub tree rooted at the node, if maintained + */ + protected int getSize() { + return size; + } + + /** + * sets a new key element for this node + * + * @param key the new key of the node + */ + public void setKey(K key) { + this.key = key; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/RBNode.java b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/RBNode.java new file mode 100644 index 00000000000..c7098abf51d --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/RBNode.java @@ -0,0 +1,150 @@ +package org.jgrapht.intervalgraph; + +import java.io.Serializable; + +/** + * Implementation of red-black tree node + * + * @param the type of the node + * @author Christoph Grüne (christophgruene) + * @author Daniel Mock (danielmock) + * @since Apr 26, 2018 + */ +class RBNode implements Serializable { + + private static final long serialVersionUID = 5674337686253743843L; + + /** + * the value of the node + */ + private V val; + /** + * node's corresponding left and right children + */ + private RBNode leftChild, rightChild; + /** + * the color of the node (important for usage in red black tree)(not necessary for all implementations) + */ + private boolean red; + /** + * the size of the sub tree rooted at this node (not necessary for all implementations) + */ + private int size; + + /** + * constructs a node object + * + * @param val the value of the node + * @param red the color of the node + * @param size the size of the sub tree rooted at the node + */ + public RBNode(V val, boolean red, int size) { + this.val = val; + this.red = red; + this.size = size; + } + + /** + * sets a new left child of the node + * + * @param leftChild the node that should be new left child + */ + protected void setLeftChild(RBNode leftChild) { + this.leftChild = leftChild; + } + + /** + * sets a new right child of the node + * + * @param rightChild the node that should be new right child + */ + protected void setRightChild(RBNode rightChild) { + this.rightChild = rightChild; + } + + /** + * sets the color for the node + * + * @param red red: true, black: false + */ + protected void setRed(boolean red) { + this.red = red; + } + + /** + * sets the color of the node to red + */ + protected void setRed() { + this.red = true; + } + + /** + * sets the color of the node to black + */ + protected void setBlack() { + this.red = false; + } + + /** + * sets a new size for the sub tree rooted at this node + * + * @param size the size of the sub tree rooted at this node + */ + protected void setSize(int size) { + this.size = size; + } + + /** + * returns whether node is red or black + * + * @return red:true, black:false + */ + protected boolean isRed() { + return red; + } + + /** + * Getter for val + * + * @return the value of the node + */ + protected V getVal() { + return val; + } + + /** + * Setter for val + * + * @param val + */ + public void setVal(V val) { + this.val = val; + } + + /** + * Getter for leftChild + * + * @return the left child of the node + */ + protected RBNode getLeftChild() { + return leftChild; + } + + /** + * Getter for rightChild + * + * @return the right child of the node + */ + protected RBNode getRightChild() { + return rightChild; + } + + /** + * Getter for size + * + * @return the size of the sub tree rooted at the node, if maintained + */ + protected int getSize() { + return size; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/RedBlackIntervalTree.java b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/RedBlackIntervalTree.java new file mode 100644 index 00000000000..d1587751be2 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/RedBlackIntervalTree.java @@ -0,0 +1,168 @@ +package org.jgrapht.intervalgraph; + +import org.jgrapht.intervalgraph.interval.Interval; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +/** + * Implementation of RedBlackIntervalTree + * This class implements the augmented interval tree. Essentially, this class updates the highValue + * after any operation on this tree. The highValue equals to the highest endpoint in the subtree + * + * @param the type of the interval + * @param the type of the key + * @param the type of the node value + * + * @author Daniel Mock (danielmock) + * @author Christoph Grüne (christophgruene) + * @since Apr 26, 2018 + */ +class RedBlackIntervalTree + , NodeValue extends IntervalTreeNodeValue, T>,T extends Comparable> + extends RedBlackTree + implements IntervalTreeInterface, Serializable { + + private static final long serialVersionUID = 4353687394654923429L; + + public RedBlackIntervalTree() { + super(); + } + + public RedBlackIntervalTree(ArrayList keys, ArrayList values) { + super(keys, values); + } + + @Override + public List> overlapsWith(Interval interval) { + List> result = new LinkedList<>(); + overlapsWith(this.getRoot(), interval, result); + return result; + } + + @Override + public List> overlapsWith(T point) { + List> result = new LinkedList<>(); + overlapsWithPoint(this.getRoot(), point, result); + return result; + } + + @Override + protected Node rotateRight(Node node) { + // Perform rotation as usual + Node result = super.rotateRight(node); + + // update hi vals + result.getVal().setHighValue(node.getVal().getHighValue()); + updateHi(node); + + return result; + } + + @Override + protected Node rotateLeft(Node node) { + // Perform rotation as usual + Node result = super.rotateLeft(node); + + // update hi vals + result.getVal().setHighValue(node.getVal().getHighValue()); + updateHi(node); + + return result; + } + + @Override + protected Node delete(Node current, K key) { + Node result = super.delete(current, key); + updateHi(result); + return result; + } + + @Override + protected Node insert(Node current, K key, NodeValue val) { + Node result = super.insert(current, key, val); + updateHi(result); + return result; + } + + @Override + protected Node balance(Node node) { + Node result = super.balance(node); + updateHi(result); + return result; + } + + // sets the hi attribute of the given node to the max of the subtree or itself + private void updateHi(Node node) { + if (node == null) { + return; + } + + T result = node.getVal().getInterval().getEnd(); + if (node.getRightChild() != null) { + result = max(result, node.getRightChild().getVal().getHighValue()); + } + + if (node.getLeftChild() != null) { + result = max(result, node.getLeftChild().getVal().getHighValue()); + } + + node.getVal().setHighValue(result); + } + + public T max(T t1, T t2) { + if (t1 == null || t2 == null) { + throw new IllegalArgumentException("Parameter cannot be null."); + } + return t1.compareTo(t2) > 0 ? t1 : t2; + } + + private void overlapsWith(Node node, Interval interval, List> result) { + if (node == null) { + return; + } + + // query starts strictly after any interval in the subtree + if (interval.getStart().compareTo(node.getVal().getHighValue()) > 0) { + return; + } + + // node and query overlap + if (node.getVal().getInterval().isIntersecting(interval)) { + result.add(node.getVal().getInterval()); + } + + // if the node starts before the query ends, check right children + if (node.getVal().getInterval().getStart().compareTo(interval.getEnd()) <= 0) { + overlapsWith(node.getRightChild(), interval, result); + } + + overlapsWith(node.getLeftChild(), interval, result); + } + + private void overlapsWithPoint(Node node, T point, List> result) { + if (node == null) { + return; + } + + // point is bigger than the endpoint of any interval in the subtree + if (point.compareTo(node.getVal().getHighValue()) > 0) { + return; + } + + // check left subtrees + overlapsWithPoint(node.getLeftChild(), point, result); + + // add node interval if it contains the query point + if (node.getVal().getInterval().contains(point)) { + result.add(node.getVal().getInterval()); + } + + // check right subtree if their start values are smaller (equal) than query point + if (point.compareTo(node.getVal().getInterval().getStart()) >= 0) { + overlapsWithPoint(node.getRightChild(), point, result); + } + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/RedBlackTree.java b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/RedBlackTree.java new file mode 100644 index 00000000000..c6e646429ba --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/RedBlackTree.java @@ -0,0 +1,616 @@ +package org.jgrapht.intervalgraph; + +import java.io.Serializable; +import java.util.*; + +/** + * Implementation of a Red-Black-Tree + * + * @param the key + * @param the value + * + * @author Daniel Mock (danielmock) + * @author Christoph Grüne (christophgruene) + * @since Apr 18, 2018 + */ +class RedBlackTree, V> implements BinarySearchTree, Serializable { + + private static final long serialVersionUID = 1199228564356373435L; + + protected Node root; + + /** + * Get the root of the red-black tree + * + * @return the root of the red-black tree + */ + public Node getRoot() { + return root; + } + + public RedBlackTree() {} + + + /** + * Construct a tree with the given keys and values. + * keys.get(i) is assigned to values.get(i) + * Runs in linear time if list is sorted, otherwise naive insertion is performed + * @param keys + * @param values + */ + public RedBlackTree(ArrayList keys, ArrayList values) { + int length = keys.size(); + + // exceptions + if (length != values.size()) { + throw new RuntimeException("Key and value list have to have same length"); + } + if (keys.size() == 0) { + return; + } + + // check if list is sorted to use efficient insertion + boolean isSorted = true; + K current = keys.get(0); + for (int i = 1; i < length; i++) { + K next = keys.get(i); + if (current.compareTo(next) > 0) { + isSorted = false; + break; + } + current = next; + } + + // use optimized insert if input is sorted, otherwise trivial insertion + if (isSorted) { + root = sortedListToBST(keys, values, 0, length - 1); + } else { + for (int i = 0; i < length; i++) { + this.insert(keys.get(i), values.get(i)); + } + } + } + + private Node sortedListToBST(ArrayList keys, ArrayList values, int start, int end) { + if (start > end) { + return null; + } + + int mid = start + (end - start) / 2; + Node node = new Node<>(keys.get(mid), values.get(mid), false, 0); // colors and size have to be updated + Node left = sortedListToBST(keys, values, start, mid - 1); + Node right = sortedListToBST(keys, values, mid + 1, end); + node.setLeftChild(left); + node.setRightChild(right); + + // color all nodes black and only the leaves red + if (left == null && right == null) { + node.setRed(); + node.setSize(0); + } else { + // update sizes + node.setSize(Math.max((left != null) ? left.getSize() : 0, right != null ? right.getSize() : 0) + 1); + } + + return node; + } + + + /** + * Returns the value associated with the given key + * + * @param key the key + * @return the value associated with the given key. If the key is not in the tree, null is returned. + * @throws IllegalArgumentException if key is null + */ + @Override + public V get(K key) { + if (key == null) { + throw new IllegalArgumentException("Key cannot be null."); + } + + Node searchResult = searchNode(key); + return searchResult != null ? searchResult.getVal() : null; + } + + /** + * Returns whether a key is contained in the tree + * + * @param key the key + * @return true if tree contains key, false otherwise + * @throws IllegalArgumentException if key is null + */ + @Override + public boolean contains(K key) { + return get(key) != null; + } + + /** + * Inserts the given (key, value) pair into the tree. If the tree contains already a symbol with the given key + * it overwrites the old value with the new. + * + * @param key the key + * @param val the value + * @throws IllegalArgumentException if key is null + */ + @Override + public void insert(K key, V val) { + if (key == null) { + throw new IllegalArgumentException("Key cannot be null."); + } + + root = insert(root, key, val); + root.setBlack(); + } + + protected Node insert(Node current, K key, V val) { + if (current == null){ + return new Node<>(key, val, true, 1); + } + + if (key.equals(current.getKey())) { + current.setVal(val); + } else if (key.compareTo(current.getKey()) < 0) { + current.setLeftChild(insert(current.getLeftChild(), key, val)); + } else { + current.setRightChild(insert(current.getRightChild(), key, val)); + } + + // Fixup + + if (isRed(current.getRightChild()) && !isRed(current.getLeftChild())) { + current = rotateLeft(current); + } + if (isRed(current.getLeftChild()) && isRed(current.getLeftChild().getLeftChild())) { + current = rotateRight(current); + } + if (isRed(current.getLeftChild()) && isRed(current.getRightChild())) { + changeColor(current); + } + current.setSize(size(current.getLeftChild()) + size(current.getRightChild()) + 1); + + return current; + } + + /** + * Removes the specified key and its associated value from this tree + * + * @param key the key + * @throws IllegalArgumentException if key is null + */ + @Override + public void delete(K key) { + if (key == null) { + throw new IllegalArgumentException("Key cannot be null."); + } + if (!contains(key)) { + return; + } + + if (!isRed(root.getLeftChild()) && !isRed(root.getRightChild())) { + root.setRed(); + } + + root = delete(root, key); + if (!isEmpty()) { + root.setBlack(); + } + } + + private boolean isEmpty() { + return root == null; + } + + private boolean isRed(Node node){ + return node != null && node.isRed(); + } + + protected Node delete(Node current, K key) { + if (key.compareTo(current.getKey()) < 0) { + if (!isRed(current.getLeftChild()) && !isRed(current.getLeftChild().getLeftChild())) { + current = moveRedLeft(current); + } + current.setLeftChild(delete(current.getLeftChild(), key)); + } else { + if (isRed(current.getLeftChild())) { + current = rotateRight(current); + } + if (key.compareTo(current.getKey()) == 0 && current.getRightChild() == null) { + return null; + } + if (!isRed(current.getRightChild()) && !isRed(current.getRightChild().getLeftChild())) { + current = moveRedRight(current); + } + if (key.compareTo(current.getKey()) == 0) { + Node node = min(current.getRightChild()); + current.setKey(node.getKey()); + current.setVal(node.getVal()); + current.setRightChild(deleteMin(current.getRightChild())); + } else { + current.setRightChild(delete(current.getRightChild(), key)); + } + } + + return balance(current); + } + + protected Node balance(Node node) { + if (isRed(node.getRightChild())) { + node = rotateLeft(node); + } + if (isRed(node.getLeftChild()) && isRed(node.getLeftChild().getLeftChild())) { + node = rotateRight(node); + } + if (isRed(node.getLeftChild()) && isRed(node.getRightChild())) { + changeColor(node); + } + + node.setSize(size(node.getLeftChild()) + size(node.getRightChild() ) + 1); + return node; + } + + /** + * Removes the smallest key and associated value from the tree. + * + * @throws NoSuchElementException if the tree is empty + */ + @Override + public void deleteMin() { + if (isEmpty()) { + throw new NoSuchElementException("empty tree"); + } + + if (!isRed(root.getLeftChild()) && !isRed(root.getRightChild())) { + root.setRed(); + } + + root = deleteMin(root); + if (!isEmpty()) { + root.setBlack(); + } + } + + private Node deleteMin(Node node) { + if (node.getLeftChild() == null) { + return null; + } + + if (!isRed(node.getLeftChild()) && !isRed(node.getLeftChild().getLeftChild())) { + root = moveRedLeft(node); + } + + node.setLeftChild(deleteMin(node.getLeftChild())); + return balance(node); + } + + /** + * Removes the largest key and associated value from the tree. + * + * @throws NoSuchElementException if the tree is empty + */ + @Override + public void deleteMax() { + if (isEmpty()) { + throw new NoSuchElementException(); + } + + if (!isRed(root.getRightChild()) && !isRed(root.getRightChild())) { + root.setRed(); + } + + root = deleteMax(root); + if (!isEmpty()) { + root.setBlack(); + } + } + + private Node deleteMax(Node node) { + if (isRed(node.getLeftChild())) { + node = rotateRight(node); + } + if (node.getRightChild() == null) { + return null; + } + if (!isRed(node.getRightChild()) && !isRed(node.getRightChild().getLeftChild())) { + node = moveRedRight(node); + } + + node.setRightChild(deleteMax(node.getRightChild())); + return balance(node); + } + + /** + * Returns the height of the BST. + * + * @return the height of the BST (a tree with 1 node has height 0) + */ + @Override + public int height() { + return height(root); + } + + private int height(Node node) { + return node == null ? -1 : 1 + Math.max(height(node.getLeftChild()), height(node.getRightChild())); + } + + /** + * Returns the smallest key in the tree. + * + * @return the smallest key in the tree + * @throws NoSuchElementException if the tree is empty + */ + @Override + public K min() { + if (isEmpty()) { + throw new NoSuchElementException("empty tree"); + } + return min(root).getKey(); + } + + private Node min(Node node) { + if (node.getLeftChild() == null) { + return node; + } + return min(node.getLeftChild()); + } + + /** + * Returns the largest key in the tree. + * + * @return the largest key in the tree + * @throws NoSuchElementException if the tree is empty + */ + @Override + public K max() { + if (isEmpty()) { + throw new NoSuchElementException("empty tree"); + } + return max(root.getRightChild()).getKey(); + } + + private Node max(Node node) { + if (node.getRightChild() == null) { + return node; + } + return max(node.getRightChild()); + } + + /** + * Returns the largest key in the tree less than or equal to key. + * + * @param key the key + * @return the largest key in the tree less than or equal to key + * @throws NoSuchElementException if there is no such key + * @throws IllegalArgumentException if key is null + */ + @Override + public K floor(K key) { + return null; + } + + /** + * Returns the smallest key in the tree greater than or equal to key. + * + * @param key the key + * @return the smallest key in the tree greater than or equal to key + * @throws NoSuchElementException if there is no such key + * @throws IllegalArgumentException if key is null + */ + @Override + public K ceiling(K key) { + return null; + } + + /** + * Return the key in the tree whose rank is k. + * This is the (k+1)st smallest key in the tree. + * + * @param k the position + * @return the key in the tree of rank k + * @throws IllegalArgumentException if k not in {0, ..., n-1} + */ + @Override + public K select(int k) { + return null; + } + + /** + * Return the number of keys in the tree strictly less than key. + * + * @param key the key + * @return the number of keys in the tree strictly less than key + * @throws IllegalArgumentException if key is null + */ + @Override + public int rank(K key) { + return 0; + } + + /** + * Returns all keys in the symbol table as an Iterable. + * To iterate over all of the keys in the symbol table named st, + * use the foreach notation: for (Key key : st.keys()). + * + * @return all keys in the symbol table as an Iterable + */ + @Override + public Iterable keys() { + return null; + } + + /** + * Returns all keys in the symbol table in the given range, + * as an Iterable. + * + * @param min minimum endpoint + * @param max maximum endpoint + * @return all keys in the sybol table between min + * (inclusive) and max (inclusive) as an Iterable + * @throws IllegalArgumentException if either min or max + * is null + */ + @Override + public Iterable keys(K min, K max) { + return null; + } + + /** + * Returns the number of keys in the symbol table in the given range. + * + * @param min minimum endpoint + * @param max maximum endpoint + * @return the number of keys in the sybol table between min + * (inclusive) and max (inclusive) + * @throws IllegalArgumentException if either min or max + * is null + */ + @Override + public int size(K min, K max) { + return 0; + } + + public int size(Node node) { + if (node == null) { + return 0; + } + + return getSize(node); + } + + /******************************************************************************************************************* + * HELPER METHODS * + ******************************************************************************************************************/ + + protected Node rotateLeft(Node node) { + Node rightChild = node.getRightChild(); + node.setRightChild(rightChild.getLeftChild()); + rightChild.setLeftChild(node); + rightChild.setRed(isRed(rightChild.getLeftChild())); + rightChild.getLeftChild().setRed(true); + rightChild.setSize(getSize(node)); + node.setSize(getSize(node.getLeftChild()) + getSize(node.getRightChild()) + 1); + return rightChild; + } + + private int getSize(Node node) { + return node != null ? node.getSize() : 0; + } + + protected Node rotateRight(Node node) { + Node leftChild = node.getLeftChild(); + node.setLeftChild(leftChild.getRightChild()); + leftChild.setRightChild(node); + leftChild.setRed(isRed(leftChild.getRightChild())); + leftChild.getRightChild().setRed(true); + leftChild.setSize(getSize(node)); + node.setSize(getSize(node.getLeftChild()) + getSize(node.getRightChild()) + 1); + return leftChild; + } + + private void changeColor(Node node) { + node.setRed(!isRed(node)); + node.getRightChild().setRed(!isRed(node.getRightChild())); + node.getLeftChild().setRed(!isRed(node.getLeftChild())); + } + + /** + * Search tree node associated to the given key + * + * @param key the key of the tree node + * @return the tree node associated to the given key, null if the tree node doesn't exist + */ + private Node searchNode(K key) { + Node current = root; + + while (current != null) { + if (current.getKey().equals(key)) { + return current; + } else if (current.getKey().compareTo(key) < 0) { + current = current.getRightChild(); + } else { + current = current.getLeftChild(); + } + } + + return null; + } + + private Node moveRedRight(Node node) { + changeColor(node); + if (isRed(node.getLeftChild().getLeftChild())) { + node = rotateRight(node); + changeColor(node); + } + + return node; + } + + private Node moveRedLeft(Node node) { + changeColor(node); + if (isRed(node.getRightChild().getLeftChild())) { + node.setRightChild(rotateRight(node.getRightChild())); + node = rotateLeft(node); + changeColor(node); + } + + return node; + } + + // returns the nodes inorder + private List> inorder() { + if (root == null) { + return new ArrayList<>(); + } + + List> result = new ArrayList<>(getSize(root)); + inorder(root, result); + return result; + } + + private void inorder(Node current, List> result) { + if (current == null) { + return; + } + + inorder(current.getLeftChild(), result); + result.add(current); + inorder(current.getRightChild(), result); + } + + public List inorderValues(){ + List> inorder = inorder(); + List result = new ArrayList<>(inorder.size()); + for (Node node: inorder) { + result.add(node.getVal()); + } + return result; + } + + private List> preorder() { + if (root == null) { + return new ArrayList<>(); + } + + List> result = new ArrayList<>(getSize(root)); + preorder(root, result); + return result; + } + + private void preorder(Node current, List> result) { + if (current == null) { + return; + } + + result.add(current); + inorder(current.getLeftChild(), result); + inorder(current.getRightChild(), result); + } + + // returns the minimum node in the subtree of input node + private Node getMin(Node node) { + while (node.getLeftChild() != null) { + node = node.getLeftChild(); + } + return node; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/interval/IntegerInterval.java b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/interval/IntegerInterval.java new file mode 100644 index 00000000000..37dda49bd72 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/interval/IntegerInterval.java @@ -0,0 +1,37 @@ +package org.jgrapht.intervalgraph.interval; + +/** + * An implementation of integer intervals + */ +public class IntegerInterval extends Interval { + + /** + * Construct an integer interval + * + * @param start interval start + * @param end interval end + * @throws IllegalArgumentException if interval start or end is null, or if interval start is greater than interval end + */ + public IntegerInterval(int start, int end) { + super(start, end); + } + + /** + * Get the duration of the interval + * + * @return the duration of the interval + */ + public int length() { + return end - start; + } + + /** + * Returns a string representation for the interval + * + * @return A string representation for the interval + */ + @Override + public String toString() { + return "IntegerInterval[" + start + ", " + end + "]"; + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/interval/Interval.java b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/interval/Interval.java new file mode 100644 index 00000000000..1e9e39a84c5 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/interval/Interval.java @@ -0,0 +1,196 @@ +package org.jgrapht.intervalgraph.interval; + +import java.util.Comparator; +import java.util.Objects; + +/** + * Model of an interval in the interval graph + * + * @param the type of the interval + */ +public class Interval> + implements + Comparable> +{ + + protected T start; + protected T end; + + /** + * Constructs an interval + * + * @param start interval start + * @param end interval end + * @throws IllegalArgumentException if interval start or end is null, or if interval start is + * greater than interval end + */ + public Interval(T start, T end) + { + if (start == null || end == null) { + throw new IllegalArgumentException("Interval start or end cannot be null."); + } + if (start.compareTo(end) > 0) { + throw new IllegalArgumentException( + "Interval start must be smaller than or equal to interval end."); + } + + this.start = start; + this.end = end; + } + + /** + * Get the start point of the interval + * + * @return the start point of the interval + */ + public T getStart() + { + return start; + } + + /** + * Get the end point of the interval + * + * @return the end point of the interval + */ + public T getEnd() + { + return end; + } + + /** + * Checks whether the other interval intersects this interval + * + * @param other The other interval + * @return true if the other interval intersects this, false otherwise + */ + public boolean isIntersecting(Interval other) + { + return this.contains(other.getStart()) || this.contains(other.getEnd()) + || other.contains(this.getStart()); + } + + /** + * Check if current interval contains the given point + * + * @param point the point to be tested + * @return true if current interval contains the given point, false otherwise + */ + public boolean contains(T point) + { + if (point == null) { + throw new IllegalArgumentException("Point to be tested cannot be null."); + } + + boolean result = point.compareTo(start) >= 0 && point.compareTo(end) <= 0; + assert result == (compareToPoint(point) == 0); + return result; + } + + /** + * Compare current interval with the given point + * + * @param point the point to be tested + * @return 0 if current interval contains the given point, comparison result with the interval + * start otherwise + */ + public int compareToPoint(T point) + { + if (point == null) { + throw new IllegalArgumentException("Point to be tested cannot be null."); + } + + int relativeStart = start.compareTo(point); + int relativeEnd = end.compareTo(point); + + if (relativeStart <= 0 && relativeEnd >= 0) { + return 0; + } else { + return relativeStart; + } + } + + /** + * Compares this interval to the other interval + * + * @param o The other interval + * @return A value < 0 if the this interval is completely left of the other interval, 0 if + * the intervals intersect, otherwise a value > 0 + */ + @Override + public int compareTo(Interval o) + { + int isLeft = end.compareTo(o.getStart()); // < 0 if this ends before other starts + int isRight = start.compareTo(o.getEnd()); // > 0 if this starts before other ends + + if (isLeft >= 0 && isRight <= 0) { + return 0; + } else if (isLeft < 0) { + return isLeft; + } else { + return isRight; + } + } + + @Override + public String toString() + { + return "Interval[" + start + ", " + end + "]"; + } + + @Override + public boolean equals(Object o) + { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Interval interval = (Interval) o; + return Objects.equals(start, interval.start) && Objects.equals(end, interval.end); + } + + @Override + public int hashCode() + { + return Objects.hash(start, end); + } + + /** + * getter for a Comparator, which only compares the starting points + * + * @param the value of the interval + * @return a starting point comparator + */ + public static > Comparator> getStartingComparator() + { + return new Comparator>() + { + @Override + public int compare(Interval o1, Interval o2) + { + return o1.getStart().compareTo(o2.getStart()); + } + }; + } + + /** + * getter for a Comparator, which only compares the ending points + * + * @param the value of the interval + * @return a ending point comparator + */ + public static > Comparator> getEndingComparator() + { + return new Comparator>() + { + + @Override + public int compare(Interval o1, Interval o2) + { + return o1.getEnd().compareTo(o2.getEnd()); + } + + }; + } + +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/interval/IntervalVertex.java b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/interval/IntervalVertex.java new file mode 100644 index 00000000000..f150244194c --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/interval/IntervalVertex.java @@ -0,0 +1,90 @@ +package org.jgrapht.intervalgraph.interval; + +import java.io.Serializable; +import java.util.Objects; + +/** + * Implementation for IntervalVertex + * This class implements the container class for vertices in an interval graph. + * + * @param the vertex + * @param the interval element + * + * @author Christoph Grüne (christophgruene) + * @since Apr 26, 2018 + */ +public class IntervalVertex> implements IntervalVertexInterface, Serializable { + private V vertex; + private Interval interval; + + private static final long serialVersionUID = 7632653463458053425L; + + /** + * Constructor for an IntervalVertex (container class for vertices in interval graphs) + * + * @param vertex the vertex + * @param interval the interval + */ + private IntervalVertex(V vertex, Interval interval) { + this.vertex = vertex; + this.interval = interval; + } + + /** + * Getter for interval + * + * @return the interval + */ + @Override + public Interval getInterval() { + return interval; + } + + /** + * Getter for vertex + * + * @return the vertex + */ + @Override + public V getVertex() { + return vertex; + } + + @Override + public String toString() + { + return "(" + vertex + "," + interval + ")"; + } + + @Override + public boolean equals(Object o) + { + if (this == o) + return true; + else if (!(o instanceof IntervalVertex)) + return false; + + @SuppressWarnings("unchecked") IntervalVertex other = (IntervalVertex) o; + return Objects.equals(vertex, other.vertex) && Objects.equals(interval, other.interval); + } + + @Override + public int hashCode() + { + return Objects.hash(vertex, interval); + } + + /** + * Creates new IntervalVertex of elements pulling of the necessity to provide corresponding types of the + * elements supplied. + * + * @param the vertex type + * @param the interval element type + * @param vertex the vertex + * @param interval the interval + * @return new pair + */ + public static > IntervalVertex of(V vertex, Interval interval) { + return new IntervalVertex<>(vertex, interval); + } +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/interval/IntervalVertexInterface.java b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/interval/IntervalVertexInterface.java new file mode 100644 index 00000000000..2abcb0d79b5 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/interval/IntervalVertexInterface.java @@ -0,0 +1,29 @@ +package org.jgrapht.intervalgraph.interval; + +/** + * Interface for IntervalVertex + * This interface provides necessary getters for the container of vertices in an interval graph + * as every vertex needs to have an interval. + * + * @param the vertex + * @param the interval element + * + * @author Christoph Grüne (christophgruene) + * @since Apr 26, 2018 + */ +public interface IntervalVertexInterface> { + + /** + * Getter for vertex + * + * @return the vertex + */ + V getVertex(); + + /** + * Getter for interval + * + * @return the interval + */ + Interval getInterval(); +} diff --git a/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/interval/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/interval/package-info.java new file mode 100644 index 00000000000..0ec015480e2 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/interval/package-info.java @@ -0,0 +1,4 @@ +/** + * Data structures for interval graphs. + */ +package org.jgrapht.intervalgraph.interval; diff --git a/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/package-info.java b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/package-info.java new file mode 100644 index 00000000000..62c5c251494 --- /dev/null +++ b/jgrapht-core/src/main/java/org/jgrapht/intervalgraph/package-info.java @@ -0,0 +1,4 @@ +/** + * Data structures for interval graphs. + */ +package org.jgrapht.intervalgraph; diff --git a/jgrapht-core/src/main/java/org/jgrapht/traverse/LexBreadthFirstIterator.java b/jgrapht-core/src/main/java/org/jgrapht/traverse/LexBreadthFirstIterator.java index 2d330fcf85f..0a88d064074 100644 --- a/jgrapht-core/src/main/java/org/jgrapht/traverse/LexBreadthFirstIterator.java +++ b/jgrapht-core/src/main/java/org/jgrapht/traverse/LexBreadthFirstIterator.java @@ -74,6 +74,68 @@ public LexBreadthFirstIterator(Graph graph) { GraphTests.requireUndirected(graph); bucketList = new BucketList(graph.vertexSet()); } + + /** + * Creates new lexicographical breadth-first iterator for {@code graph}. + * + * @param graph the graph to be iterated. + * @param startingVertex the initial vertex. + */ + public LexBreadthFirstIterator(Graph graph, V startingVertex) { + super(graph); + GraphTests.requireUndirected(graph); + + Set copyOfSet = new HashSet<>(); + + copyOfSet.addAll(graph.vertexSet()); + bucketList = new BucketList(copyOfSet, startingVertex); + } + + + /** + * Creates new lexicographical breadth-first iterator with a static priority list for {@code graph}. + * + * @param graph the graph to be iterated. + * @param priority the vertex array sorted by their priorities. + */ + public LexBreadthFirstIterator(Graph graph, HashMap priority) { + super(graph); + GraphTests.requireUndirected(graph); + + Set copyOfSet = new HashSet<>(); + + copyOfSet.addAll(graph.vertexSet()); + bucketList = new BucketList(copyOfSet, new PriorityComparator(priority)); + } + + /** + * Creates new lexicographical breadth-first iterator with a static priority list for {@code graph}. + * + * This is used for the LBFS* variant top detect interval graphs + * + * @param graph the graph to be iterated. + * @param priorityA The A priority list + * @param priorityB The B priority list + * @param neighborIndexA The A neighboring list + * @param neighborIndexB The B neighboring list + * @param ASets The A sets + * @param BSets The B sets + */ + public LexBreadthFirstIterator(Graph graph, + HashMap priorityA, + HashMap priorityB, + HashMap neighborIndexA, + HashMap neighborIndexB, + HashMap> ASets, + HashMap> BSets) { + super(graph); + GraphTests.requireUndirected(graph); + + Set copyOfSet = new HashSet<>(); + + copyOfSet.addAll(graph.vertexSet()); + bucketList = new BucketList(copyOfSet, priorityA, priorityB, neighborIndexA, neighborIndexB, ASets, BSets); + } /** * Checks whether there exist unvisited vertices. @@ -182,19 +244,96 @@ class BucketList { * the vertex in constant time. */ private Map bucketMap; - + + /** + * Comparator used for tiebreaking when multiple vertices have the same label + */ + private Comparator priorityComparator = null; + + /** + * LBFS* static parameters + */ + private HashMap neighborIndexA = null; + private HashMap neighborIndexB = null; + + private HashMap> ASets = null; + private HashMap> BSets = null; + + private HashMap priorityA = null; + private HashMap priorityB = null; + /** * Creates a {@code BucketList} with a single bucket and all specified {@code vertices} in it. * * @param vertices the vertices of the graph, that should be stored in the {@code head} bucket. */ BucketList(Collection vertices) { - head = new Bucket(vertices); + head = new Bucket(vertices, priorityComparator); // we do not need a comparator bucketMap = new HashMap<>(vertices.size()); for (V vertex : vertices) { bucketMap.put(vertex, head); } } + + /** + * Creates a {@code BucketList} with a single bucket and all specified {@code vertices} in it. + * + * @param vertices the vertices of the graph, that should be stored in the {@code head} bucket. + * @param startingVertex the initial vertex. + */ + BucketList(Collection vertices, V startingVertex) { + bucketMap = new HashMap<>(vertices.size()); + + // Split off starting vertex into its own bucket + vertices.remove(startingVertex); + head = new Bucket(startingVertex, priorityComparator); + head.insertBefore(new Bucket(vertices, priorityComparator)); + + bucketMap.put(startingVertex, head); + for (V vertex : vertices) { + bucketMap.put(vertex, head.next); + } + } + + /** + * Creates a {@code BucketList} with a single bucket and all specified {@code vertices} in it. + * + * @param vertices the vertices of the graph, that should be stored in the {@code head} bucket. + * @param priorityComparator a comparator which defines a priority for tiebreaking. + * @param startingVertex the initial vertex. + */ + BucketList(Collection vertices, Comparator priorityComparator) { + bucketMap = new HashMap<>(vertices.size()); + + // Split off starting vertex into its own bucket + head = new Bucket(vertices, priorityComparator); + + for (V vertex : vertices) { + bucketMap.put(vertex, head); + } + } + + /** + * Creates a {@code BucketList} with a single bucket and all specified {@code vertices} in it. + * + * @param vertices the vertices of the graph, that should be stored in the {@code head} bucket. + */ + BucketList(Collection vertices, HashMap priorityA, HashMap priorityB, HashMap neighborIndexA, HashMap neighborIndexB, HashMap> ASets, HashMap> BSets) { + this.neighborIndexA = neighborIndexA; + this.neighborIndexB = neighborIndexB; + this.ASets = ASets; + this.BSets = BSets; + this.priorityA = priorityA; + this.priorityB = priorityB; + + bucketMap = new HashMap<>(vertices.size()); + + head = new Bucket(vertices, new PriorityComparator(priorityA), new PriorityComparator(priorityB)); + + for (V vertex : vertices) { + bucketMap.put(vertex, head); + } + } /** * Checks whether there exists a bucket with the specified {@code vertex}. @@ -219,7 +358,14 @@ boolean containsBucketWith(V vertex) { */ V poll() { if (bucketMap.size() > 0) { - V res = head.poll(); + V res = null; + + if(neighborIndexA == null) { + res = head.poll(); + } else { + res = head.poll(neighborIndexA, neighborIndexB, ASets, BSets, priorityA, priorityB); + } + bucketMap.remove(res); if (head.isEmpty()) { head = head.next; @@ -251,7 +397,13 @@ void updateBuckets(Set vertices) { bucketMap.put(vertex, bucket.prev); } else { visitedBuckets.add(bucket); - Bucket newBucket = new Bucket(vertex); + Bucket newBucket; + if (priorityB != null) { + newBucket = new Bucket(vertex, new PriorityComparator(priorityA), new PriorityComparator(priorityB)); + } + else{ + newBucket = new Bucket(vertex, priorityComparator); + } newBucket.insertBefore(bucket); bucketMap.put(vertex, newBucket); if (head == bucket) { @@ -285,15 +437,24 @@ private class Bucket { /** * Set of vertices currently stored in this bucket. */ - private Set vertices; + private Queue vertices; + /** + * Set of vertices currently stored in this bucket (sorted by other order). + */ + private Queue verticesB = null; /** * Creates a new bucket with all {@code vertices} stored in it. * * @param vertices vertices to store in this bucket. */ - Bucket(Collection vertices) { - this.vertices = new HashSet<>(vertices); + Bucket(Collection vertices, Comparator c) { + if(c == null) { + this.vertices = new PriorityQueue<>(); + } else { + this.vertices = new PriorityQueue<>(c); + } + this.vertices.addAll(vertices); } /** @@ -301,10 +462,44 @@ private class Bucket { * * @param vertex the vertex to store in this bucket. */ - Bucket(V vertex) { - this.vertices = new HashSet<>(); + Bucket(V vertex, Comparator c) { + if(c == null) { + this.vertices = new PriorityQueue<>(); + } else { + this.vertices = new PriorityQueue<>(c); + } vertices.add(vertex); } + + /** + * LBFS*-variants + */ + + /** + * Creates a new bucket with all {@code vertices} stored in it. + * + * @param vertices vertices to store in this bucket. + */ + Bucket(Collection vertices, Comparator compA, Comparator compB) { + this.vertices = new PriorityQueue<>(compA); + this.verticesB = new PriorityQueue<>(compB); + + this.vertices.addAll(vertices); + this.verticesB.addAll(vertices); + } + + /** + * Creates a new Bucket with a single {@code vertex} in it. + * + * @param vertex the vertex to store in this bucket. + */ + Bucket(V vertex, Comparator compA, Comparator compB) { + this.vertices = new PriorityQueue<>(compA); + this.verticesB = new PriorityQueue<>(compB); + + vertices.add(vertex); + verticesB.add(vertex); + } /** * Removes the {@code vertex} from this bucket. @@ -313,6 +508,10 @@ private class Bucket { */ void removeVertex(V vertex) { vertices.remove(vertex); + + if(verticesB != null) { + verticesB.remove(vertex); + } } /** @@ -352,6 +551,10 @@ void insertBefore(Bucket bucket) { */ void addVertex(V vertex) { vertices.add(vertex); + + if(verticesB != null) { + verticesB.add(vertex); + } } /** @@ -363,11 +566,41 @@ V poll() { if (vertices.isEmpty()) { return null; } else { - V vertex = vertices.iterator().next(); - vertices.remove(vertex); + V vertex = vertices.poll(); return vertex; } } + + /** + * Retrieves one vertex from this bucket (according to LBFS*). + * + * @return vertex, that was removed from this bucket, null if the bucket was empty. + */ + V poll(HashMap neighborIndexA, HashMap neighborIndexB, HashMap> ASets, HashMap> BSets, HashMap priorityA, HashMap priorityB) { + if (vertices.isEmpty()) { + return null; + } else { + V alpha = vertices.peek(); + V beta = verticesB.peek(); + + if(neighborIndexA.get(alpha) > priorityA.get(alpha)) { + vertices.remove(beta); + return verticesB.poll(); // return Beta + } else if(neighborIndexB.get(beta) > priorityB.get(beta)) { + verticesB.remove(beta); + return vertices.poll(); // return Alpha + } else if(BSets.get(beta).isEmpty() || !ASets.get(alpha).isEmpty()) { + vertices.remove(beta); + return verticesB.poll(); // return Beta + } else if(neighborIndexA.get(BSets.get(beta).iterator().next()) == priorityA.get(alpha)) { + vertices.remove(beta); + return verticesB.poll(); // return Beta + } else { + verticesB.remove(beta); + return vertices.poll(); // return Alpha + } + } + } /** * Checks whether this bucket is empty. @@ -379,4 +612,39 @@ boolean isEmpty() { } } } + + class PriorityComparator implements Comparator + { + /** + * Contains the priorities of the vertices. + */ + private final HashMap priority; + + /** + * Creates a new priority comparator for the vertex set with given priorities. + * + * @param priority the (integer-valued) priorities of the vertices. + * @throws IllegalArgumentException if the priorities are null. + */ + public PriorityComparator(HashMap priority) throws IllegalArgumentException { + if(priority == null) { + throw new IllegalArgumentException("Priority map must not be null"); + } + this.priority = priority; + } + + @Override + /** + * Compares the priorities of the given vertices. + * + * @param vertex1 the first vertex to be compared. + * @param vertex2 the second vertex to be compared. + * + * @return Returns a positive integer (zero/a negative integer) if the priority of vertex1 is smaller (equal to/higher) than the one of vertex2. + */ + public int compare(V vertex1, V vertex2) + { + return priority.get(vertex2) - priority.get(vertex1); + } + } } diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/color/DecompositionTreeColourTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/color/DecompositionTreeColourTest.java new file mode 100644 index 00000000000..e47e0f87db6 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/color/DecompositionTreeColourTest.java @@ -0,0 +1,80 @@ +package org.jgrapht.alg.color; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.jgrapht.Graph; +import org.jgrapht.Graphs; +import org.jgrapht.alg.interfaces.VertexColoringAlgorithm; +import org.jgrapht.alg.interfaces.VertexColoringAlgorithm.Coloring; +import org.jgrapht.graph.DefaultEdge; +import org.jgrapht.graph.SimpleGraph; +import org.junit.Test; +/** + * Coloring tests + * + * @author Suchanda Bhattacharyya (dia007) + */ +public class DecompositionTreeColourTest { + + protected VertexColoringAlgorithm getAlgorithm(Graph graph, Map > decompositionMap) + { + return new DecompositionTreeColour<>(graph, decompositionMap); + } + //need to add more graphs + + final protected List createGraph1() + { + + Graph graph = new SimpleGraph<>(DefaultEdge.class) ; + Map > decompositionMap = new HashMap<>(); + List returnArrayList= new ArrayList<>(); + + Graphs.addEdgeWithVertices(graph, 0, 1); + Graphs.addEdgeWithVertices(graph, 1, 2); + Graphs.addEdgeWithVertices(graph, 2, 3); + Graphs.addEdgeWithVertices(graph, 3, 4); + Graphs.addEdgeWithVertices(graph, 4, 5); + + int v1 = 11,v2=22,v3=33,v4=44; + Set set0 = new HashSet<>(); + Set set1 = new HashSet<>(); + Set set2 = new HashSet<>(); + Set set3 = new HashSet<>(); + Set set4 = new HashSet<>(); + Set set5 = new HashSet<>(); + set0.add(Collections.emptySet()); set1.add(v1); set2.add(v1); set2.add(v2); set2.add(v3);set3.add(v2); set3.add(v3); set4.add(v2); set5.add(v2); set5.add(v4); + + decompositionMap.put(0,set0); decompositionMap.put(1,set1 ); decompositionMap.put(2,set2 );decompositionMap.put(3,set3 );decompositionMap.put(4,set4 );decompositionMap.put(5,set5 ); + returnArrayList.add(graph); returnArrayList.add(decompositionMap); + + return returnArrayList; + } + + + + @Test + public void testGreedy1() + + { + List returnArrayList= createGraph1(); + Graph newGraph = (Graph) returnArrayList.get(0); + Map > newDecompositionMap = (Map>) returnArrayList.get(1); + + Coloring coloring = new DecompositionTreeColour<>(newGraph,newDecompositionMap).getColoring(); + assertEquals(3, coloring.getNumberColors()); + Map colors = coloring.getColors(); + + + //iterate over all edges/vertices and check if the test can be done better + assertNotEquals(colors.get(11).intValue(), colors.get(22).intValue(), colors.get(33).intValue()); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/decompostion/IntervalGraphDecompositionBuilderTest.java b/jgrapht-core/src/test/java/org/jgrapht/alg/decompostion/IntervalGraphDecompositionBuilderTest.java new file mode 100644 index 00000000000..f1e69a982f9 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/decompostion/IntervalGraphDecompositionBuilderTest.java @@ -0,0 +1,105 @@ +package org.jgrapht.alg.decompostion; + +import static org.junit.Assert.*; + +import java.util.*; + +import org.jgrapht.*; +import org.jgrapht.alg.decomposition.*; +import org.jgrapht.graph.*; +import org.jgrapht.intervalgraph.interval.*; +import org.junit.*; + +public class IntervalGraphDecompositionBuilderTest +{ + + + /** + * Interval Graph representation of the graph to test: + * _ ___________ ___________ + * ____________ ___________ + * _____________ ___________ + * ... + */ + @Test + public void testIntervalgraphDecompositionForRegularGraphs() + { + //graph + Graph g = new DefaultUndirectedGraph<>(DefaultEdge.class); + g.addVertex(-1); + for(int i = 0; i<10; i++) + g.addVertex(i); + + for(int i = 0; i<10; i++) { + for(int j = i+1; j<10; j++) { + if((i%2)==0 && (j%2)==0) + g.addEdge(i, j); + + if((i%2)==1 && (j%2)==0 && i decomp = IntervalGraphNiceDecompositionBuilder.create(g); + assertNotNull("graph was detected as not an interval graph", decomp); + + //test for nice decomposition + NiceDecompositionBuilderTestUtil.testNiceDecomposition(decomp.getDecomposition(), decomp.getMap(), decomp.getRoot()); + NiceDecompositionBuilderTestUtil.testDecomposition(g, decomp.getDecomposition(), decomp.getMap()); + } + + @Test + public void testIntervalgraphDecompositionForIntervalGraphs() + { + //TODO + } + + @Test + public void testIntervalgraphDecompositionForSortedIntervalLists() + { + //TODO + } + + /** + * Test for the create method of lists of intervals + * Representation: + * + * __ __ __ + * __ + * __ + * ... + * __ + * ___ + * ____ + * ... + */ + @Test + public void testIntervalgraphDecompositionForIntervalLists() + { + List> list = new ArrayList>(); + //unconnected + list.add(new Interval(-4,-3)); + list.add(new Interval(-2,-1)); + //just path + for(int i = 0; i<10; i++) + { + list.add(new Interval(i,i+1)); + } + //and to spice it up, a clique + for(int i = 0; i<10; i++) + { + list.add(new Interval(10,10+i)); + } + IntervalGraphNiceDecompositionBuilder> decompalg = IntervalGraphNiceDecompositionBuilder.create(list); + Graph decomp = decompalg.getDecomposition(); + Map>> map = decompalg.getMap(); + Integer root = decompalg.getRoot(); + NiceDecompositionBuilderTestUtil.testNiceDecomposition(decomp,map,root); + + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/alg/decompostion/NiceDecompositionBuilderTestUtil.java b/jgrapht-core/src/test/java/org/jgrapht/alg/decompostion/NiceDecompositionBuilderTestUtil.java new file mode 100644 index 00000000000..c9985d2aa88 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/alg/decompostion/NiceDecompositionBuilderTestUtil.java @@ -0,0 +1,114 @@ +package org.jgrapht.alg.decompostion; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.*; +import java.util.Map.*; + +import org.jgrapht.*; +import org.jgrapht.graph.*; + +public class NiceDecompositionBuilderTestUtil +{ + private NiceDecompositionBuilderTestUtil() {} + + /** + * tests whether the tree decomposition (decomposition, map) with root is nice + * + * @param decomposition the tree decomposition to check + * @param map the map of the tree decomposition + * @param root the root of the tree decomposition + */ + public static void testNiceDecomposition(Graph decomposition, Map> map, V root){ + + Queue queue = new LinkedList(); + + //test and add root + assertTrue(root+" is no valid root" + + "\n in decomposition "+decomposition + + "\n and map"+map, map.get(root).isEmpty()); + queue.add(root); + + while(!queue.isEmpty()) + { + V current = queue.poll(); + List successor = Graphs.successorListOf(decomposition, current); + if(successor.size() == 0 && map.get(current).isEmpty()) continue; //leaf node + if(successor.size() == 1) //forget or introduce + { + V next = successor.get(0); + queue.add(next); + Set union = new HashSet(map.get(current)); + union.addAll(map.get(next)); + if(union.size() == map.get(next).size() || union.size() == map.get(current).size()) + { + if(map.get(current).size() == map.get(next).size()-1) continue; //introduce + else if(map.get(current).size()-1 == map.get(next).size()) continue; //forget + } + } + if(successor.size() == 2) //join + { + V first = successor.get(0); + V second = successor.get(1); + queue.add(first); + queue.add(second); + Set union = new HashSet(map.get(current)); + union.addAll(map.get(first)); + union.addAll(map.get(second)); + if(union.size() == map.get(current).size() + && union.size() == map.get(first).size() + && union.size() == map.get(second).size()) + continue; //join node! + } + assertFalse("Vertex Set "+current+" is not a valid node for a nice decomposition" + + "\nin decomposition "+decomposition + + "\nwith map "+map, true); //no valid node! + } + assertTrue(true); + } + + /** + * Test whether (decomposition, map) a tree decomposition of oldGraph is + * + * @param oldGraph the graph of the tree decomposition + * @param decomposition the tree decomposition + * @param map the map from vertices to the tree decomposition to the sets of the tree decomposition + */ + public static void testDecomposition(Graph oldGraph, Graph decomposition, Map> map){ + Set edgeSet = oldGraph.edgeSet(); + Set vertexSet = decomposition.vertexSet(); + //Every edge is represented + for(F e : edgeSet) + { + boolean hasVertex = false; + for(V v : vertexSet) + { + if(map.get(v).contains(oldGraph.getEdgeSource(e)) && map.get(v).contains(oldGraph.getEdgeTarget(e))) { + hasVertex = true; + continue; + } + } + assertTrue("Edge "+e+" is not found" + + "\nin graph "+decomposition + + "\nwith map "+map, hasVertex); + } + //every vertex has non-empty connected set of vertex sets + Set oldVertexSet = oldGraph.vertexSet(); + for(W w : oldVertexSet) + { + Set keySet = new HashSet(); + for(Entry> entry : map.entrySet()) + { + if(entry.getValue().contains(w)) + keySet.add(entry.getKey()); + } + //not empty + assertFalse("Vertex "+w+" is not represented\n in decomposition "+decomposition+"\n and map "+map, keySet.isEmpty()); + //connected + Graph subgraph = new AsSubgraph(decomposition,keySet); + assertTrue("Vertex "+w+" is not connected\n in decomposition "+decomposition+"\n and map "+map,GraphTests.isConnected(subgraph)); + } + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/intervalgraph/CenteredIntervalTreeStructureTest.java b/jgrapht-core/src/test/java/org/jgrapht/intervalgraph/CenteredIntervalTreeStructureTest.java new file mode 100644 index 00000000000..55cce98477a --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/intervalgraph/CenteredIntervalTreeStructureTest.java @@ -0,0 +1,64 @@ +package org.jgrapht.intervalgraph; + +import org.jgrapht.intervalgraph.interval.IntegerInterval; +import org.jgrapht.intervalgraph.interval.Interval; +import org.junit.Before; +import org.junit.Test; + +import java.util.LinkedList; +import java.util.List; + +import static org.junit.Assert.*; + +public class CenteredIntervalTreeStructureTest { + + private List> list = new LinkedList<>(); + + @Test + public void testCompareToPoint() { + IntegerInterval interval = new IntegerInterval(0, 1); + assertEquals(1, interval.compareToPoint(-1)); + assertEquals(0, interval.compareToPoint(0)); + assertEquals(0, interval.compareToPoint(1)); + assertEquals(-1, interval.compareToPoint(2)); + } + + @Test + public void testEmptyTree() { + CenteredIntervalTree tree = new CenteredIntervalTree<>(list); + assertEquals(0, tree.intersections(-2).size()); + assertEquals(0, tree.intersections(0).size()); + assertEquals(0, tree.intersections((2)).size()); + } + + @Test + public void testSingleInterval() { + list.add(new IntegerInterval(0, 2)); + CenteredIntervalTree tree = new CenteredIntervalTree<>(list); + assertEquals(0, tree.intersections(-1).size()); + assertEquals(1, tree.intersections(0).size()); + assertEquals(1, tree.intersections(1).size()); + assertEquals(1, tree.intersections(2).size()); + assertEquals(0, tree.intersections(3).size()); + } + + @Test + public void testPath() { + list.add(new IntegerInterval(0, 1)); + list.add(new IntegerInterval(1, 2)); + list.add(new IntegerInterval(2, 3)); + list.add(new IntegerInterval(3, 4)); + list.add(new IntegerInterval(4, 5)); + list.add(new IntegerInterval(5, 6)); + CenteredIntervalTree tree = new CenteredIntervalTree<>(list); + assertEquals(0, tree.intersections(-1).size()); + assertEquals(1, tree.intersections(0).size()); + assertEquals(2, tree.intersections(1).size()); + assertEquals(2, tree.intersections(2).size()); + assertEquals(2, tree.intersections(3).size()); + assertEquals(2, tree.intersections(4).size()); + assertEquals(2, tree.intersections(5).size()); + assertEquals(1, tree.intersections(6).size()); + assertEquals(0, tree.intersections(7).size()); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/intervalgraph/CenteredIntervalTreeTest.java b/jgrapht-core/src/test/java/org/jgrapht/intervalgraph/CenteredIntervalTreeTest.java new file mode 100644 index 00000000000..0412c88c7b4 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/intervalgraph/CenteredIntervalTreeTest.java @@ -0,0 +1,64 @@ +package org.jgrapht.intervalgraph; + +import org.jgrapht.intervalgraph.interval.IntegerInterval; +import org.jgrapht.intervalgraph.interval.Interval; +import org.junit.Before; +import org.junit.Test; + +import java.util.LinkedList; +import java.util.List; + +import static org.junit.Assert.*; + +public class CenteredIntervalTreeTest { + + private List> list = new LinkedList<>(); + + @Test + public void testCompareToPoint() { + IntegerInterval interval = new IntegerInterval(0, 1); + assertEquals(1, interval.compareToPoint(-1)); + assertEquals(0, interval.compareToPoint(0)); + assertEquals(0, interval.compareToPoint(1)); + assertEquals(-1, interval.compareToPoint(2)); + } + + @Test + public void testEmptyTree() { + CenteredIntervalTree tree = new CenteredIntervalTree<>(list); + assertEquals(0, tree.intersections(-2).size()); + assertEquals(0, tree.intersections(0).size()); + assertEquals(0, tree.intersections((2)).size()); + } + + @Test + public void testSingleInterval() { + list.add(new IntegerInterval(0, 2)); + CenteredIntervalTree tree = new CenteredIntervalTree<>(list); + assertEquals(0, tree.intersections(-1).size()); + assertEquals(1, tree.intersections(0).size()); + assertEquals(1, tree.intersections(1).size()); + assertEquals(1, tree.intersections(2).size()); + assertEquals(0, tree.intersections(3).size()); + } + + @Test + public void testPath() { + list.add(new IntegerInterval(0, 1)); + list.add(new IntegerInterval(1, 2)); + list.add(new IntegerInterval(2, 3)); + list.add(new IntegerInterval(3, 4)); + list.add(new IntegerInterval(4, 5)); + list.add(new IntegerInterval(5, 6)); + CenteredIntervalTree tree = new CenteredIntervalTree<>(list); + assertEquals(0, tree.intersections(-1).size()); + assertEquals(1, tree.intersections(0).size()); + assertEquals(2, tree.intersections(1).size()); + assertEquals(2, tree.intersections(2).size()); + assertEquals(2, tree.intersections(3).size()); + assertEquals(2, tree.intersections(4).size()); + assertEquals(2, tree.intersections(5).size()); + assertEquals(1, tree.intersections(6).size()); + assertEquals(0, tree.intersections(7).size()); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/intervalgraph/IntervalGraphTest.java b/jgrapht-core/src/test/java/org/jgrapht/intervalgraph/IntervalGraphTest.java new file mode 100644 index 00000000000..e60ddac48d9 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/intervalgraph/IntervalGraphTest.java @@ -0,0 +1,483 @@ +package org.jgrapht.intervalgraph; + +import org.jgrapht.EdgeFactory; +import org.jgrapht.graph.ClassBasedEdgeFactory; +import org.jgrapht.graph.DefaultEdge; +import org.jgrapht.graph.DefaultWeightedEdge; +import org.jgrapht.intervalgraph.interval.Interval; +import org.jgrapht.intervalgraph.interval.IntervalVertex; +import org.jgrapht.intervalgraph.interval.IntervalVertexInterface; +import org.junit.Before; +import org.junit.Test; + +import java.util.Set; + +import static org.junit.Assert.*; + +/** + * @author Abdallah Atouani + * @since 02 May 2018 + */ +public class IntervalGraphTest { + + private IntervalGraph, DefaultEdge, Integer, Integer> intervalGraph; + private IntervalGraph, DefaultWeightedEdge, Integer, Integer> weightedIntervalGraph; + + @Before + public void setUp() { + EdgeFactory, DefaultEdge> edgeFactory1 = new ClassBasedEdgeFactory<>(DefaultEdge.class); + EdgeFactory, DefaultWeightedEdge> edgeFactory2 = new ClassBasedEdgeFactory<>(DefaultWeightedEdge.class); + + intervalGraph = new IntervalGraph<>(edgeFactory1, false); + + weightedIntervalGraph = new IntervalGraph<>(edgeFactory2, true); + } + + @Test(expected = IllegalArgumentException.class) + public void addEdge() { + Interval interval1 = new Interval<>(1, 2); + IntervalVertex vertex1 = IntervalVertex.of(1, interval1); + intervalGraph.addVertex(vertex1); + + Interval interval2 = new Interval<>(1, 10); + IntervalVertex vertex2 = IntervalVertex.of(2, interval2); + intervalGraph.addVertex(vertex2); + + intervalGraph.addEdge(vertex1, vertex2); + } + + @Test(expected = IllegalArgumentException.class) + public void removeEdge() { + Interval interval1 = new Interval<>(1, 2); + IntervalVertex vertex1 = IntervalVertex.of(1, interval1); + intervalGraph.addVertex(vertex1); + + Interval interval2 = new Interval<>(1, 10); + IntervalVertex vertex2 = IntervalVertex.of(2, interval2); + intervalGraph.addVertex(vertex2); + + intervalGraph.removeEdge(vertex1, vertex2); + } + + @Test + public void containsEdge() { + Interval interval1 = new Interval<>(5, 19); + IntervalVertex vertex1 = IntervalVertex.of(1, interval1); + intervalGraph.addVertex(vertex1); + weightedIntervalGraph.addVertex(vertex1); + + Interval interval2 = new Interval<>(9, 100); + IntervalVertex vertex2 = IntervalVertex.of(2, interval2); + intervalGraph.addVertex(vertex2); + weightedIntervalGraph.addVertex(vertex2); + + Interval interval3 = new Interval<>(6, 11); + IntervalVertex vertex3 = IntervalVertex.of(3, interval3); + intervalGraph.addVertex(vertex3); + weightedIntervalGraph.addVertex(vertex3); + + Interval interval4 = new Interval<>(1000, 1001); + IntervalVertex vertex4 = IntervalVertex.of(4, interval4); + intervalGraph.addVertex(vertex4); + weightedIntervalGraph.addVertex(vertex4); + + assertTrue(intervalGraph.containsEdge(vertex2, vertex1)); + assertTrue(intervalGraph.containsEdge(vertex1, vertex2)); + assertTrue(intervalGraph.containsEdge(vertex1, vertex3)); + assertTrue(intervalGraph.containsEdge(vertex3, vertex1)); + assertTrue(intervalGraph.containsEdge(vertex2, vertex3)); + assertTrue(intervalGraph.containsEdge(vertex3, vertex2)); + assertFalse(intervalGraph.containsEdge(vertex1, vertex4)); + assertFalse(intervalGraph.containsEdge(vertex2, vertex4)); + assertFalse(intervalGraph.containsEdge(vertex3, vertex4)); + + assertFalse(intervalGraph.containsEdge(vertex1, vertex1)); + assertFalse(intervalGraph.containsEdge(vertex2, vertex2)); + assertFalse(intervalGraph.containsEdge(vertex3, vertex3)); + assertFalse(intervalGraph.containsEdge(vertex4, vertex4)); + + assertTrue(weightedIntervalGraph.containsEdge(vertex2, vertex1)); + assertTrue(weightedIntervalGraph.containsEdge(vertex1, vertex2)); + assertTrue(weightedIntervalGraph.containsEdge(vertex1, vertex3)); + assertTrue(weightedIntervalGraph.containsEdge(vertex3, vertex1)); + assertTrue(weightedIntervalGraph.containsEdge(vertex2, vertex3)); + assertTrue(weightedIntervalGraph.containsEdge(vertex3, vertex2)); + assertFalse(weightedIntervalGraph.containsEdge(vertex1, vertex4)); + assertFalse(weightedIntervalGraph.containsEdge(vertex2, vertex4)); + assertFalse(weightedIntervalGraph.containsEdge(vertex3, vertex4)); + + assertFalse(weightedIntervalGraph.containsEdge(vertex1, vertex1)); + assertFalse(weightedIntervalGraph.containsEdge(vertex2, vertex2)); + assertFalse(weightedIntervalGraph.containsEdge(vertex3, vertex3)); + assertFalse(weightedIntervalGraph.containsEdge(vertex4, vertex4)); + } + + @Test + public void getAllEdges() { + Interval interval1 = new Interval<>(5, 19); + IntervalVertex vertex1 = IntervalVertex.of(1, interval1); + intervalGraph.addVertex(vertex1); + weightedIntervalGraph.addVertex(vertex1); + + Interval interval2 = new Interval<>(9, 100); + IntervalVertex vertex2 = IntervalVertex.of(2, interval2); + intervalGraph.addVertex(vertex2); + weightedIntervalGraph.addVertex(vertex2); + + Interval interval3 = new Interval<>(6, 11); + IntervalVertex vertex3 = IntervalVertex.of(3, interval3); + intervalGraph.addVertex(vertex3); + weightedIntervalGraph.addVertex(vertex3); + + Interval interval4 = new Interval<>(1000, 1001); + IntervalVertex vertex4 = IntervalVertex.of(4, interval4); + intervalGraph.addVertex(vertex4); + weightedIntervalGraph.addVertex(vertex4); + + Set edgeSet = intervalGraph.getAllEdges(vertex1, vertex2); + assertTrue(edgeSet.contains(intervalGraph.getEdge(vertex1, vertex2))); + assertEquals(edgeSet.size(), 1); + + edgeSet = intervalGraph.getAllEdges(vertex1, vertex4); + assertTrue(edgeSet.isEmpty()); + + Set weightedEdgeSet = weightedIntervalGraph.getAllEdges(vertex1, vertex2); + assertTrue(weightedEdgeSet.contains(weightedIntervalGraph.getEdge(vertex1, vertex2))); + assertEquals(weightedEdgeSet.size(), 1); + + weightedEdgeSet = weightedIntervalGraph.getAllEdges(vertex1, vertex4); + assertTrue(weightedEdgeSet.isEmpty()); + } + + @Test + public void addVertex() { + Interval interval1 = new Interval<>(1, 2); + IntervalVertex vertex1 = IntervalVertex.of(1, interval1); + intervalGraph.addVertex(vertex1); + weightedIntervalGraph.addVertex(vertex1); + + IntervalVertex vertex2 = IntervalVertex.of(1, interval1); + intervalGraph.addVertex(vertex2); + weightedIntervalGraph.addVertex(vertex2); + + assertTrue(intervalGraph.vertexSet().contains(vertex1)); + assertTrue(intervalGraph.vertexSet().contains(vertex2)); + assertEquals(intervalGraph.vertexSet().size(), 1); + assertTrue(weightedIntervalGraph.vertexSet().contains(vertex1)); + assertTrue(weightedIntervalGraph.vertexSet().contains(vertex2)); + assertEquals(weightedIntervalGraph.vertexSet().size(), 1); + + IntervalVertex vertex3 = IntervalVertex.of(2, interval1); + intervalGraph.addVertex(vertex3); + weightedIntervalGraph.addVertex(vertex3); + + assertTrue(intervalGraph.vertexSet().contains(vertex3)); + assertEquals(intervalGraph.vertexSet().size(), 2); + assertTrue(weightedIntervalGraph.vertexSet().contains(vertex3)); + assertEquals(weightedIntervalGraph.vertexSet().size(), 2); + + Interval interval2 = new Interval<>(2, 10); + IntervalVertex vertex4 = IntervalVertex.of(2, interval2); + intervalGraph.addVertex(vertex4); + weightedIntervalGraph.addVertex(vertex4); + + assertTrue(intervalGraph.vertexSet().contains(vertex4)); + assertEquals(intervalGraph.vertexSet().size(), 3); + assertTrue(weightedIntervalGraph.vertexSet().contains(vertex4)); + assertEquals(weightedIntervalGraph.vertexSet().size(), 3); + } + + @Test + public void removeVertex() { + Interval interval1 = new Interval<>(3, 20); + IntervalVertex vertex1 = IntervalVertex.of(1, interval1); + intervalGraph.addVertex(vertex1); + weightedIntervalGraph.addVertex(vertex1); + + Interval interval2 = new Interval<>(4, 100); + IntervalVertex vertex2 = IntervalVertex.of(2, interval2); + intervalGraph.addVertex(vertex2); + weightedIntervalGraph.addVertex(vertex2); + + assertTrue(intervalGraph.vertexSet().contains(vertex1)); + assertTrue(intervalGraph.vertexSet().contains(vertex2)); + assertTrue(weightedIntervalGraph.vertexSet().contains(vertex1)); + assertTrue(weightedIntervalGraph.vertexSet().contains(vertex2)); + + intervalGraph.removeVertex(vertex1); + weightedIntervalGraph.removeVertex(vertex1); + + assertTrue(intervalGraph.vertexSet().contains(vertex2)); + assertFalse(intervalGraph.vertexSet().contains(vertex1)); + assertTrue(weightedIntervalGraph.vertexSet().contains(vertex2)); + assertFalse(weightedIntervalGraph.vertexSet().contains(vertex1)); + + assertEquals(intervalGraph.vertexSet().size(), 1); + assertEquals(weightedIntervalGraph.vertexSet().size(), 1); + + intervalGraph.removeVertex(vertex2); + weightedIntervalGraph.removeVertex(vertex2); + + assertFalse(intervalGraph.vertexSet().contains(vertex1)); + assertFalse(intervalGraph.vertexSet().contains(vertex2)); + assertFalse(weightedIntervalGraph.vertexSet().contains(vertex1)); + assertFalse(weightedIntervalGraph.vertexSet().contains(vertex2)); + + assertEquals(intervalGraph.vertexSet().size(), 0); + assertEquals(weightedIntervalGraph.vertexSet().size(), 0); + } + + @Test + public void containsVertex() { + Interval interval1 = new Interval<>(8, 9); + IntervalVertex vertex1 = IntervalVertex.of(1, interval1); + intervalGraph.addVertex(vertex1); + weightedIntervalGraph.addVertex(vertex1); + + Interval interval2 = new Interval<>(27, 56); + IntervalVertex vertex2 = IntervalVertex.of(2, interval2); + intervalGraph.addVertex(vertex2); + weightedIntervalGraph.addVertex(vertex2); + + assertTrue(intervalGraph.containsVertex(vertex1)); + assertTrue(intervalGraph.containsVertex(vertex2)); + assertEquals(intervalGraph.vertexSet().size(), 2); + assertTrue(weightedIntervalGraph.containsVertex(vertex1)); + assertTrue(weightedIntervalGraph.containsVertex(vertex2)); + assertEquals(weightedIntervalGraph.vertexSet().size(), 2); + + intervalGraph.removeVertex(vertex1); + weightedIntervalGraph.removeVertex(vertex1); + + assertFalse(intervalGraph.containsVertex(vertex1)); + assertTrue(intervalGraph.containsVertex(vertex2)); + assertEquals(intervalGraph.vertexSet().size(), 1); + assertFalse(weightedIntervalGraph.containsVertex(vertex1)); + assertTrue(weightedIntervalGraph.containsVertex(vertex2)); + assertEquals(weightedIntervalGraph.vertexSet().size(), 1); + } + + @Test + public void getEdgeSource() { + Interval interval1 = new Interval<>(10, 14); + IntervalVertex vertex1 = IntervalVertex.of(2, interval1); + intervalGraph.addVertex(vertex1); + + Interval interval2 = new Interval<>(1, 18); + IntervalVertex vertex2 = IntervalVertex.of(1, interval2); + intervalGraph.addVertex(vertex2); + + Interval interval3 = new Interval<>(-3, 2); + IntervalVertex vertex3 = IntervalVertex.of(3, interval3); + intervalGraph.addVertex(vertex3); + + DefaultEdge edge = intervalGraph.getEdge(vertex1, vertex2); + DefaultEdge edge1 = intervalGraph.getEdge(vertex2, vertex3); + + assertEquals(intervalGraph.getEdgeSource(edge), vertex2); + assertEquals(intervalGraph.getEdgeSource(edge1), vertex3); + } + + @Test + public void getEdgeTarget() { + Interval interval1 = new Interval<>(2, 4); + IntervalVertex vertex1 = IntervalVertex.of(1, interval1); + intervalGraph.addVertex(vertex1); + + Interval interval2 = new Interval<>(1, 7); + IntervalVertex vertex2 = IntervalVertex.of(3, interval2); + intervalGraph.addVertex(vertex2); + + Interval interval3 = new Interval<>(0, 4); + IntervalVertex vertex3 = IntervalVertex.of(2, interval3); + intervalGraph.addVertex(vertex3); + + DefaultEdge edge = intervalGraph.getEdge(vertex1, vertex2); + DefaultEdge edge1 = intervalGraph.getEdge(vertex2, vertex3); + + assertEquals(intervalGraph.getEdgeTarget(edge), vertex1); + assertEquals(intervalGraph.getEdgeTarget(edge1), vertex2); + + } + + @Test + public void edgeSet() { + Interval interval1 = new Interval<>(29, 30); + IntervalVertex vertex1 = IntervalVertex.of(3, interval1); + intervalGraph.addVertex(vertex1); + + Interval interval2 = new Interval<>(27, 56); + IntervalVertex vertex2 = IntervalVertex.of(1, interval2); + intervalGraph.addVertex(vertex2); + + assertEquals(1, intervalGraph.edgeSet().size()); + + } + + @Test + public void vertexSet() { + Interval interval1 = new Interval<>(-10, 1); + IntervalVertex vertex1 = IntervalVertex.of(29, interval1); + intervalGraph.addVertex(vertex1); + + assertTrue(intervalGraph.vertexSet().contains(vertex1)); + + Interval interval2 = new Interval<>(-38, 0); + IntervalVertex vertex2 = IntervalVertex.of(1, interval2); + intervalGraph.addVertex(vertex2); + + assertTrue(intervalGraph.vertexSet().contains(vertex1)); + assertTrue(intervalGraph.vertexSet().contains(vertex2)); + assertEquals(intervalGraph.vertexSet().size(), 2); + + Interval interval3 = new Interval<>(100, 293); + IntervalVertex vertex3 = IntervalVertex.of(1, interval3); + intervalGraph.addVertex(vertex3); + + assertTrue(intervalGraph.vertexSet().contains(vertex1)); + assertTrue(intervalGraph.vertexSet().contains(vertex2)); + assertTrue(intervalGraph.vertexSet().contains(vertex3)); + assertEquals(intervalGraph.vertexSet().size(), 3); + + intervalGraph.removeVertex(vertex2); + intervalGraph.removeVertex(vertex3); + + assertTrue(intervalGraph.vertexSet().contains(vertex1)); + assertFalse(intervalGraph.vertexSet().contains(vertex2)); + assertFalse(intervalGraph.vertexSet().contains(vertex3)); + assertEquals(intervalGraph.vertexSet().size(), 1); + + intervalGraph.removeVertex(vertex1); + + assertTrue(intervalGraph.vertexSet().isEmpty()); + } + + @Test + public void getEdgeWeight() { + Interval interval1 = new Interval<>(-3, 1); + IntervalVertex vertex1 = IntervalVertex.of(29, interval1); + intervalGraph.addVertex(vertex1); + + Interval interval2 = new Interval<>(0, 3); + IntervalVertex vertex2 = IntervalVertex.of(7, interval2); + intervalGraph.addVertex(vertex2); + + Interval interval3 = new Interval<>(1, 29); + IntervalVertex vertex3 = IntervalVertex.of(9, interval3); + intervalGraph.addVertex(vertex3); + + DefaultEdge edge = intervalGraph.getEdge(vertex3, vertex1); + DefaultEdge edge1 = intervalGraph.getEdge(vertex2, vertex1); + + assertEquals(intervalGraph.getEdgeWeight(edge), 1, 0); + assertEquals(intervalGraph.getEdgeWeight(edge1), 1, 0); + } + + @Test + public void cloneIntervalGraph() { + IntervalGraph, DefaultEdge, Integer, Integer> clonedIntervalGraph = + (IntervalGraph, DefaultEdge, Integer, Integer>) intervalGraph.clone(); + + assertEquals(clonedIntervalGraph.vertexSet(), intervalGraph.vertexSet()); + assertEquals(clonedIntervalGraph.edgeSet(), intervalGraph.edgeSet()); + assertEquals(clonedIntervalGraph.getEdgeFactory(), intervalGraph.getEdgeFactory()); + assertEquals(clonedIntervalGraph.isWeighted(), intervalGraph.isWeighted()); + assertTrue(clonedIntervalGraph.equals(intervalGraph)); + } + + @Test + public void degreeOf() { + Interval interval1 = new Interval<>(-3, 1); + IntervalVertex vertex1 = IntervalVertex.of(1, interval1); + intervalGraph.addVertex(vertex1); + + Interval interval2 = new Interval<>(0, 4); + IntervalVertex vertex2 = IntervalVertex.of(2, interval2); + intervalGraph.addVertex(vertex2); + + Interval interval3 = new Interval<>(2, 29); + IntervalVertex vertex3 = IntervalVertex.of(3, interval3); + intervalGraph.addVertex(vertex3); + + Interval interval4 = new Interval<>(348, 2394); + IntervalVertex vertex4 = IntervalVertex.of(4, interval4); + intervalGraph.addVertex(vertex4); + + assertEquals(intervalGraph.degreeOf(vertex2), 2); + assertEquals(intervalGraph.degreeOf(vertex1), 1); + assertEquals(intervalGraph.degreeOf(vertex3), 1); + assertEquals(intervalGraph.degreeOf(vertex4), 0); + } + + @Test + public void edgesOf() { + Interval interval1 = new Interval<>(-10, 19); + IntervalVertex vertex1 = IntervalVertex.of(1, interval1); + intervalGraph.addVertex(vertex1); + + Interval interval2 = new Interval<>(9, 100); + IntervalVertex vertex2 = IntervalVertex.of(2, interval2); + intervalGraph.addVertex(vertex2); + + Interval interval3 = new Interval<>(12, 39); + IntervalVertex vertex3 = IntervalVertex.of(3, interval3); + intervalGraph.addVertex(vertex3); + + Interval interval4 = new Interval<>(1000, 1001); + IntervalVertex vertex4 = IntervalVertex.of(4, interval4); + intervalGraph.addVertex(vertex4); + + Set edgesOfVertex1 = intervalGraph.edgesOf(vertex1); + assertTrue(edgesOfVertex1.contains(intervalGraph.getEdge(vertex1, vertex2))); + assertTrue(edgesOfVertex1.contains(intervalGraph.getEdge(vertex2, vertex1))); + assertTrue(edgesOfVertex1.contains(intervalGraph.getEdge(vertex1, vertex3))); + assertEquals(edgesOfVertex1.size(), 2); + + Set edgesOfVertex2 = intervalGraph.edgesOf(vertex2); + assertTrue(edgesOfVertex2.contains(intervalGraph.getEdge(vertex1, vertex2))); + assertTrue(edgesOfVertex2.contains(intervalGraph.getEdge(vertex2, vertex1))); + assertTrue(edgesOfVertex2.contains(intervalGraph.getEdge(vertex2, vertex3))); + assertEquals(edgesOfVertex1.size(), 2); + + Set edgesOfVertex3 = intervalGraph.edgesOf(vertex3); + assertTrue(edgesOfVertex3.contains(intervalGraph.getEdge(vertex2, vertex3))); + assertTrue(edgesOfVertex3.contains(intervalGraph.getEdge(vertex3, vertex2))); + assertTrue(edgesOfVertex3.contains(intervalGraph.getEdge(vertex1, vertex3))); + assertEquals(edgesOfVertex1.size(), 2); + + Set edgesOfVertex4 = intervalGraph.edgesOf(vertex4); + assertTrue(edgesOfVertex4.isEmpty()); + } + + @Test + public void isWeighted() { + assertFalse(intervalGraph.isWeighted()); + assertTrue(weightedIntervalGraph.isWeighted()); + } + + @Test + public void setEdgeWeight() { + Interval interval1 = new Interval<>(2, 19); + IntervalVertex vertex1 = IntervalVertex.of(2, interval1); + weightedIntervalGraph.addVertex(vertex1); + + Interval interval2 = new Interval<>(-4, 8); + IntervalVertex vertex2 = IntervalVertex.of(1, interval2); + weightedIntervalGraph.addVertex(vertex2); + + Interval interval3 = new Interval<>(11, 198); + IntervalVertex vertex3 = IntervalVertex.of(3, interval3); + weightedIntervalGraph.addVertex(vertex3); + + DefaultWeightedEdge edge1 = weightedIntervalGraph.getEdge(vertex1, vertex2); + DefaultWeightedEdge edge2 = weightedIntervalGraph.getEdge(vertex1, vertex3); + + weightedIntervalGraph.setEdgeWeight(edge1, 3.0); + weightedIntervalGraph.setEdgeWeight(edge2, 93.0); + + assertEquals(3.0, weightedIntervalGraph.getEdgeWeight(edge1), 0.001); + assertEquals(93.0, weightedIntervalGraph.getEdgeWeight(edge2),0.001); + + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/intervalgraph/IntervalTreeStructureTest.java b/jgrapht-core/src/test/java/org/jgrapht/intervalgraph/IntervalTreeStructureTest.java new file mode 100644 index 00000000000..308a1ca5c8c --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/intervalgraph/IntervalTreeStructureTest.java @@ -0,0 +1,40 @@ +package org.jgrapht.intervalgraph; + +import org.jgrapht.intervalgraph.interval.IntegerInterval; +import org.junit.Before; +import org.junit.Test; + +import java.util.LinkedList; +import java.util.List; + +import static org.junit.Assert.*; + +public class IntervalTreeStructureTest { + + private List sorted = new LinkedList<>(); + private IntervalTreeStructure tree = new IntervalTreeStructure<>(); + + @Before + public void setUp() throws Exception { + for (int i = 0; i < 20; i++) { + IntegerInterval interval = new IntegerInterval(i, i+3); + tree.add(interval); + sorted.add(interval); + } + } + + @Test + public void test1() { + for (int i = 3; i < 20; i++) { + assertEquals("loop " + i, 4, tree.overlapsWithPoint(i).size()); + } + } + + @Test + public void testIntervalOverlap() { + assertEquals(4, tree.overlapsWith(new IntegerInterval(0,3)).size()); + assertEquals(1 , tree.overlapsWith(new IntegerInterval(0,0)).size()); + assertEquals(0, tree.overlapsWith(new IntegerInterval(-3, -1)).size()); + assertEquals(20, tree.overlapsWith(new IntegerInterval(-5, 20)).size()); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/intervalgraph/RedBlackIntervalTreeTest.java b/jgrapht-core/src/test/java/org/jgrapht/intervalgraph/RedBlackIntervalTreeTest.java new file mode 100644 index 00000000000..7096e75740e --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/intervalgraph/RedBlackIntervalTreeTest.java @@ -0,0 +1,54 @@ +package org.jgrapht.intervalgraph; + +import org.jgrapht.intervalgraph.interval.Interval; +import org.junit.Before; +import org.junit.Test; + +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +public class RedBlackIntervalTreeTest { + + List> sorted = new LinkedList<>(); + RedBlackIntervalTree>, IntervalTreeNodeValue, Integer>, Integer> tree = new RedBlackIntervalTree<>(); + + @Before + public void setUp() throws Exception { + for (int i = 0; i < 20; i++) { + Interval interval = new Interval<>(i, i + 3); + tree.insert(new IntervalTreeNodeKey<>(interval, getComparator()), new IntervalTreeNodeValue<>(interval)); + sorted.add(interval); + } + } + + @Test + public void testInorder() { + List, Integer>> result = tree.inorderValues(); + for (int i1 = 0, resultSize = result.size(); i1 < resultSize; i1++) { + Interval i = result.get(i1).getInterval(); + assertEquals("fault at " + i1, i, sorted.get(i1)); + } + + + + tree.delete(new IntervalTreeNodeKey<>(new Interval<>(5, 8), getComparator())); + assertFalse(tree.contains(new IntervalTreeNodeKey<>(new Interval<>(5, 8), getComparator()))); + assertEquals(Integer.valueOf(19 + 3), tree.getRoot().getVal().getHighValue()); + } + + private Comparator> getComparator() { + return (o1, o2) -> { + int startCompare = o1.getStart().compareTo(o2.getStart()); + if (startCompare != 0) { + return startCompare; + } else { + return o1.getEnd().compareTo(o2.getEnd()); + } + }; + } +} + diff --git a/jgrapht-core/src/test/java/org/jgrapht/intervalgraph/RedBlackTreeTest.java b/jgrapht-core/src/test/java/org/jgrapht/intervalgraph/RedBlackTreeTest.java new file mode 100644 index 00000000000..f374a0ae5f2 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/intervalgraph/RedBlackTreeTest.java @@ -0,0 +1,149 @@ +package org.jgrapht.intervalgraph; + +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNull; + +public class RedBlackTreeTest { + + private RedBlackTree redBlackTree = new RedBlackTree<>(); + + @Before + public void setUp() { + redBlackTree.insert(13, 13); + redBlackTree.insert(8, 8); + redBlackTree.insert(17, 17); + redBlackTree.insert(1, 1); + redBlackTree.insert(11, 11); + redBlackTree.insert(15, 15); + redBlackTree.insert(25, 25); + redBlackTree.insert(6, 6); + redBlackTree.insert(22, 22); + redBlackTree.insert(27, 27); + } + + @Test + public void testGet() { + assertEquals(8, redBlackTree.get(8).intValue()); + assertEquals(6, redBlackTree.get(6).intValue()); + assertNull(redBlackTree.get(30)); + assertNull(redBlackTree.get(35)); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetNegative() { + redBlackTree.get(null); + } + + @Test + public void testContains(){ + assertTrue(redBlackTree.contains(1)); + assertTrue(redBlackTree.contains(11)); + assertFalse(redBlackTree.contains(30)); + assertFalse(redBlackTree.contains(35)); + } + + @Test(expected = IllegalArgumentException.class) + public void testContainsNegative() { + redBlackTree.contains(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testInsertNegative() { + redBlackTree.insert(null, null); + } + + @Test + public void testDelete() { + assertTrue(redBlackTree.contains(15)); + redBlackTree.delete(15); + redBlackTree.delete(15); + assertFalse(redBlackTree.contains(15)); + + redBlackTree.delete(30); + redBlackTree.delete(35); + } + + @Test(expected = IllegalArgumentException.class) + public void testDeleteNegative() { + redBlackTree.delete(null); + } + + // Test important properties of red-black tree + + /** + * The root is black + */ + @Test + public void testBlackRoot() { + assertFalse(redBlackTree.getRoot().isRed()); + } + + /** + * If a node is red, then both its children are black + */ + @Test + public void testBlackChildren() { + testBlackChildren(redBlackTree.getRoot()); + } + + private void testBlackChildren(Node currentNode) { + Node leftChild = currentNode.getLeftChild(); + if (leftChild != null) { + if (currentNode.isRed()) { + assertFalse(leftChild.isRed()); + } + testBlackChildren(leftChild); + } + + Node rightChild = currentNode.getRightChild(); + if (rightChild != null) { + if (currentNode.isRed()) { + assertFalse(rightChild.isRed()); + } + testBlackChildren(rightChild); + } + } + + @Test + public void testListConstructor() { + ArrayList keyList = new ArrayList<>(20); + for (int i = 0; i < 20; i++) { + keyList.add(i, i); + } + RedBlackTree tree = new RedBlackTree<>(keyList, keyList); + List result = tree.inorderValues(); + for (int i = 0; i < 20; i++) { + assertEquals(i, (int) result.get(i)); + } + } + + + /** + * Every path from a given node to any of its descendant leaves contains the same number of black nodes + */ + @Test + public void testBlackNodeNumber() { + assertEquals(countLeftChildren(redBlackTree.getRoot(), 0), countRightChildren(redBlackTree.getRoot(), 0)); + } + + private int countLeftChildren(Node node, int currentBlackNumber) { + currentBlackNumber = node.isRed() ? currentBlackNumber : currentBlackNumber + 1; + return node.getLeftChild() == null ? currentBlackNumber : countLeftChildren(node.getLeftChild(), currentBlackNumber); + } + + private int countRightChildren(Node node, int currentBlackNumber) { + currentBlackNumber = node.isRed() ? currentBlackNumber : currentBlackNumber + 1; + return node.getRightChild() == null ? currentBlackNumber : countRightChildren(node.getRightChild(), currentBlackNumber); + } + +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/intervalgraph/interval/IntegerIntervalTest.java b/jgrapht-core/src/test/java/org/jgrapht/intervalgraph/interval/IntegerIntervalTest.java new file mode 100644 index 00000000000..9b62f675e30 --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/intervalgraph/interval/IntegerIntervalTest.java @@ -0,0 +1,45 @@ +package org.jgrapht.intervalgraph.interval; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class IntegerIntervalTest { + + private IntegerInterval i00 = new IntegerInterval(0,0); + private IntegerInterval i01 = new IntegerInterval(0,1); + private IntegerInterval i56 = new IntegerInterval(5,6); + + @Test + public void testIntersecting() { + assertTrue(i00.isIntersecting(i01)); + assertTrue(i01.isIntersecting(i01)); + assertFalse(i56.isIntersecting(i01)); + } + + @Test + public void testContains() { + assertTrue(i00.contains(0)); + assertTrue(i01.contains(0)); + assertFalse(i00.contains(1)); + } + + @Test + public void testCompareTo() { + assertEquals(i00.compareTo(i01), 0); + assertEquals(i01.compareTo(i00), 0); + assertEquals(i56.compareTo(i01), 1); + assertEquals(i00.compareTo(i56), -1); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidInterval1() { + new Interval(null, null); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidInterval2() { + new IntegerInterval(6, 5); + } +} diff --git a/jgrapht-core/src/test/java/org/jgrapht/intervalgraph/interval/IntervalGraphRecognizerTest.java b/jgrapht-core/src/test/java/org/jgrapht/intervalgraph/interval/IntervalGraphRecognizerTest.java new file mode 100644 index 00000000000..dcce418daea --- /dev/null +++ b/jgrapht-core/src/test/java/org/jgrapht/intervalgraph/interval/IntervalGraphRecognizerTest.java @@ -0,0 +1,262 @@ +package org.jgrapht.intervalgraph.interval; + +import org.jgrapht.*; +import org.jgrapht.alg.intervalgraph.IntervalGraphRecognizer; +import org.jgrapht.alg.util.*; +import org.jgrapht.generate.*; +import org.jgrapht.graph.DefaultEdge; +import org.jgrapht.graph.SimpleGraph; + +import org.jgrapht.graph.builder.GraphBuilder; +import org.jgrapht.intervalgraph.IntervalGraph; +import org.junit.Test; + +import static org.junit.Assert.*; + +import java.util.*; + +public class IntervalGraphRecognizerTest { + + /** + * The graph with no vertex or edge is trivially an interval graph + */ + @Test + public void testEmptyGraph() { + Graph emptyGraph = new SimpleGraph<>(DefaultEdge.class); + + IntervalGraphRecognizer recognizer = new IntervalGraphRecognizer<>(emptyGraph); + assertTrue(recognizer.isIntervalGraph()); + } + + /** + * First of five cases given by Lekkerkerker and Boland + * which at least one of them appears as an induced subgraph in every non-interval graph + * + * A biclaw with AT is a 3-star graph (or claw) with an additional neighbor for every leave + */ + @Test + public void testForbiddenSubgraphBiclawWithAT() { + GraphBuilder> builder + = new SimpleGraph<>((sourceVertex, targetVertex) -> new DefaultEdge()).createBuilder(DefaultEdge.class); + + builder.addEdge(0, 1); + builder.addEdge(0, 2); + builder.addEdge(0, 3); + + builder.addEdge(1, 4); + builder.addEdge(2, 5); + builder.addEdge(3, 6); + + IntervalGraphRecognizer recognizer = new IntervalGraphRecognizer<>(builder.build()); + assertFalse(recognizer.isIntervalGraph()); + } + + /** + * Second of five cases given by Lekkerkerker and Boland + * which at least one of them appears as an induced subgraph in every non-interval graph + */ + @Test + public void testForbiddenSubgraphLekkerkerkerBoland() { + + + GraphBuilder> builder + = new SimpleGraph<>((sourceVertex, targetVertex) -> new DefaultEdge()).createBuilder(DefaultEdge.class); + + builder.addEdge(0, 1); + builder.addEdge(0, 2); + builder.addEdge(0, 3); + builder.addEdge(0, 4); + builder.addEdge(0, 5); + + builder.addEdge(1, 2); + builder.addEdge(2, 3); + builder.addEdge(3, 4); + builder.addEdge(4, 5); + + builder.addEdge(3, 6); + + IntervalGraphRecognizer recognizer = new IntervalGraphRecognizer<>(builder.build()); + assertFalse(recognizer.isIntervalGraph()); + } + + public void testForbiddenSubgraphLekkerkerkerBolandFamily(int n) { + GraphBuilder> builder + = new SimpleGraph<>((sourceVertex, targetVertex) -> new DefaultEdge()).createBuilder(DefaultEdge.class); + + builder.addEdge(0, 1); + + for(int i = 3; i < n; i++) { + builder.addEdge(1, i); + builder.addEdge(i - 1, i); + } + + builder.addEdge(n - 1, n); + + IntervalGraphRecognizer recognizer = new IntervalGraphRecognizer<>(builder.build()); + assertEquals("n was " + n, n <= 4, recognizer.isIntervalGraph()); + } + + /** + * Third of five cases given by Lekkerkerker and Boland + * which at least one of them appears as an induced subgraph in every non-interval graph + */ + @Test + public void testForbiddenSubgraphLekkerkerkerBolandFamily() { + for(int n = 4; n < 20; n++) { + testForbiddenSubgraphLekkerkerkerBolandFamily(n); + } + } + + + public void isForbiddenSubgraphLekkerkerkerBolandFamily2(int n) { + GraphBuilder> builder + = new SimpleGraph<>((sourveVertex, targetVertex) -> new DefaultEdge()).createBuilder(DefaultEdge.class); + + //asteroidal triple: 1,3,5 + builder.addEdge(0, 1); + builder.addEdge(1, 2); + builder.addEdge(2, 3); + builder.addEdge(3, 4); + builder.addEdge(4, 5); + builder.addEdge(5, n); + + for (int i=5; i recognizer = new IntervalGraphRecognizer<>(builder.build()); + assertFalse(recognizer.isIntervalGraph()); + } + + /** + * Fourth of five cases given by Lekkerkerker and Boland + * which at least one of them appears as an induced subgraph in every non-interval graph + */ + @Test + public void testForbiddenSubgraphLekkerkerkerBolandFamily2() { + for(int n = 5; n < 20; n++) { + testForbiddenSubgraphLekkerkerkerBolandFamily(n); + } + } + + public boolean isCnAnIntervalGraph(int n) { + GraphBuilder> builder + = new SimpleGraph<>((sourceVertex, targetVertex) -> new DefaultEdge()).createBuilder(DefaultEdge.class); + + for(int i = 0; i < n; i++) { + builder.addEdge(i, (i + 1) % n); + } + + IntervalGraphRecognizer recognizer = new IntervalGraphRecognizer<>(builder.build()); + return recognizer.isIntervalGraph(); + } + + /** + * Fifth of five cases given by Lekkerkerker and Boland + * which at least one of them appears as an induced subgraph in every non-interval graph + * + * A circle graph of length n + */ + @Test + public void testForbiddenSubgraphCn() { + for(int n = 2; n < 20; n++) { + assertEquals("Testing C_" + n, n < 4, isCnAnIntervalGraph(n)); + } + } + + public void isCompleteAnIntervalGraph(int n) { + CompleteGraphGenerator cgg = + new CompleteGraphGenerator(n); + Graph cg = + new SimpleGraph<>(DefaultEdge.class); + cgg.generateGraph(cg, new IntegerVertexFactory(), null); + + //Every complete Graph is an interval graph + IntervalGraphRecognizer recognizer = new IntervalGraphRecognizer<>(cg); + assertTrue(recognizer.isIntervalGraph()); + + //also test the sorting algorithm for intervals + ArrayList> intervalsStartSort = recognizer.getIntervalsSortedByStartingPoint(); + ArrayList> intervalsEndSort = recognizer.getIntervalsSortedByEndingPoint(); + intervalsStartSort.sort(Interval.getEndingComparator()); + assertEquals(intervalsStartSort, intervalsEndSort); + } + + /* + * Every complete graph is an interval graph + */ + @Test + public void completeGraphTest() + { + for(int i=0; i<20; i++) { + isCompleteAnIntervalGraph(i); + } + + } + + public void isUnconnectedAnIntervalGraph(int n) { + GraphBuilder> builder + = new SimpleGraph<>((sourceVertex, targetVertex) -> new DefaultEdge()).createBuilder(DefaultEdge.class); + + for(int i = 0; i < n; i++) { + builder.addVertex(i); + } + + //Every complete Graph is an interval graph + IntervalGraphRecognizer recognizer = new IntervalGraphRecognizer<>(builder.build()); + assertTrue(recognizer.isIntervalGraph()); + + //also test the sorting algorithm for intervals + ArrayList> intervalsStartSort = recognizer.getIntervalsSortedByStartingPoint(); + ArrayList> intervalsEndSort = recognizer.getIntervalsSortedByEndingPoint(); + intervalsStartSort.sort(Interval.getEndingComparator()); + assertEquals(intervalsStartSort, intervalsEndSort); + } + + /* + * Every unconnected Graph is an interval graph + */ + @Test + public void unconnectedGraphTest() + { + for(int i=0; i<20; i++) { + isUnconnectedAnIntervalGraph(i); + } + + } + + public void isLinearAnIntervalGraph(int n) { + GraphBuilder> builder + = new SimpleGraph<>((sourceVertex, targetVertex) -> new DefaultEdge()).createBuilder(DefaultEdge.class); + + for(int i = 0; i < n-1; i++) { + builder.addEdge(i, (i + 1)); + } + + //Every complete Graph is an interval graph + IntervalGraphRecognizer recognizer = new IntervalGraphRecognizer<>(builder.build()); + assertTrue(recognizer.isIntervalGraph()); + + //also test the sorting algorithm for intervals + ArrayList> intervalsStartSort = recognizer.getIntervalsSortedByStartingPoint(); + ArrayList> intervalsEndSort = recognizer.getIntervalsSortedByEndingPoint(); + intervalsStartSort.sort(Interval.getEndingComparator()); + assertEquals(intervalsStartSort, intervalsEndSort); + } + + /* + * Every linear graph is an interval graph + */ + @Test + public void linearGraphTest() + { + for(int i=4; i<20; i++) { + isLinearAnIntervalGraph(i); + } + + } +}