Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the Time Complexity of size() for Sets in Java?

I know, it seems like a stupid question, you would expect that the time complexity of size() on any collection would be O(1) - but I'm finding that an "optimization" in my code which requires a call to size() is actually slowing things down.

So, what is the time complexity of size() for Sets in Java?

My code is an implementation of a recursive algorithm to find the maximal cliques in a graph (not important). Basically, the optimization just checks whether two Sets have equal size (these Sets are being constructed either way), and allows for only one more recursive call (stopping the recursion after that) if they are equal in size.

Here is a (simplified) version of my code:

        private static void recursivelyFindMaximalCliques(Set<Integer> subGraph, Set<Integer> candidates, Set<Integer> notCandidates) {
    boolean noFurtherCliques = false;

    Iterator<Integer> candidateIterator = candidates.iterator();
    while (candidateIterator.hasNext()) {
        int nextCandidate = candidateIterator.next();
        candidateIterator.remove();
        subGraph.add(nextCandidate);

        Set<Integer> neighbors = getNeighbors(nextCandidate);
        Set<Integer> newCandidates = calculateIntersection(candidates, neighbors);
        Set<Integer> newNotCandidates = calculateIntersection(notCandidates, neighbors);

        //if (newCandidates.size() == candidates.size())
        //  noFurtherCliques = true;
        recursivelyFindMaximalCliques(subGraph, newCandidates, newNotCandidates);
        //if (noFurtherCliques)
        //  return;

        subGraph.set.remove(nextCandidate);
        notCandidates.set.add(nextCandidate);
    }
}

The lines I have commented out are the ones in question - you can see that they check if the sets newCandidates and candidates are the same size, and if they are, the recursion is only allowed to go one level deeper.

When the lines are uncommented, the code runs about 10% slower - this is true whether the sets used are HashSets, TreeSets, or LinkedHashSets. This makes no sense, since those lines ensure that there will be FEWER recursive calls.

The only thing I can assume is that the call to size() on the sets is taking a long time. Does calling size() on Sets in Java take longer than O(1)?

EDIT

Since some people have asked, here is calculateIntersection():

private static IntegerSet calculateIntersection(Set<Integer> setA, Set<Integer> setB) {
    if (setA.size() == 0 || setB.size() == 0)
        return new Set<Integer>();

    Set<Integer> intersection = new Set<Integer>(); //Replace this with TreeSet, HashSet, or LinkedHashSet, whichever is being used
    intersection.addAll(setA);
    intersection.retainAll(setB);
    return intersection;
}

SECOND EDIT Here is the full code, if you like. I hesitated to post it, since it's long and nasty, but people asked, so here it is:

public class CliqueFindingAlgorithm {

private static class IntegerSet {
    public Set<Integer> set = new TreeSet<Integer>();  //Or whatever Set is being used
}


private static ArrayList<IntegerSet> findMaximalCliques(UndirectedGraph graph) {
    ArrayList<IntegerSet> cliques = new ArrayList<IntegerSet>();
    IntegerSet subGraph = new IntegerSet();
    IntegerSet candidates = new IntegerSet();
    IntegerSet notCandidates = new IntegerSet();

    for (int vertex = 0; vertex < graph.getNumVertices(); vertex++) {
        candidates.set.add(vertex);
    }

    recursivelyFindMaximalCliques(cliques, graph, subGraph, candidates, notCandidates);
    return cliques;
}


private static void recursivelyFindMaximalCliques(ArrayList<IntegerSet> cliques, UndirectedGraph graph, 
        IntegerSet subGraph, IntegerSet candidates, IntegerSet notCandidates) {
    boolean noFurtherCliques = false;

    Iterator<Integer> candidateIterator = candidates.set.iterator();
    while (candidateIterator.hasNext()) {
        int nextCandidate = candidateIterator.next();
        candidateIterator.remove();
        subGraph.set.add(nextCandidate);

        IntegerSet neighbors = new IntegerSet();
        neighbors.set = graph.getNeighbors(nextCandidate);
        IntegerSet newCandidates = calculateIntersection(candidates, neighbors);
        IntegerSet newNotCandidates = calculateIntersection(notCandidates, neighbors);

        if (newCandidates.set.size() == candidates.set.size())
            noFurtherCliques = true;
        recursivelyFindMaximalCliques(cliques, graph, subGraph, newCandidates, newNotCandidates);
        if (noFurtherCliques)
            return;

        subGraph.set.remove(nextCandidate);
        notCandidates.set.add(nextCandidate);
    }

    if (notCandidates.set.isEmpty()) {
        IntegerSet clique = new IntegerSet();
        clique.set.addAll(subGraph.set);
        cliques.add(clique);
    }
}


private static IntegerSet calculateIntersection(IntegerSet setA, IntegerSet setB) {
    if (setA.set.size() == 0 || setB.set.size() == 0)
        return new IntegerSet();

    IntegerSet intersection = new IntegerSet();
    intersection.set.addAll(setA.set);
    intersection.set.retainAll(setB.set);
    return intersection;
}

}

public class UndirectedGraph {

// ------------------------------ PRIVATE VARIABLES ------------------------------

private ArrayList<TreeMap<Integer, Double>> neighborLists;
private int numEdges;


// ------------------------------ CONSTANTS ------------------------------  

// ------------------------------ CONSTRUCTORS ------------------------------

public UndirectedGraph(int numVertices) {
    this.neighborLists = new ArrayList<TreeMap<Integer, Double>>(numVertices);
    this.numEdges = 0;
    for (int i = 0; i < numVertices; i++) {
        this.neighborLists.add(new TreeMap<Integer, Double>());
    }
}


// ------------------------------ PUBLIC METHODS ------------------------------


public void addEdge(int vertexA, int vertexB, double edgeWeight) {
    TreeMap<Integer, Double> vertexANeighbors = this.neighborLists.get(vertexA);
    TreeMap<Integer, Double> vertexBNeighbors = this.neighborLists.get(vertexB);

    vertexANeighbors.put(vertexB, edgeWeight);
    vertexBNeighbors.put(vertexA, edgeWeight);
    this.numEdges++;
}


public List<Integer> computeCommonNeighbors(int vertexA, int vertexB) {
    List<Integer> commonNeighbors = new ArrayList<Integer>();
    Iterator<Integer> iteratorA = this.getNeighbors(vertexA).iterator();
    Iterator<Integer> iteratorB = this.getNeighbors(vertexB).iterator();

    if (iteratorA.hasNext() && iteratorB.hasNext()) {
        int nextNeighborA = iteratorA.next();
        int nextNeighborB = iteratorB.next();
        while(true) {

            if (nextNeighborA == nextNeighborB) {
                commonNeighbors.add(nextNeighborA);
                if (iteratorA.hasNext() && iteratorB.hasNext()) {
                    nextNeighborA = iteratorA.next();
                    nextNeighborB = iteratorB.next();
                }
                else
                    break;
            }

            else if (nextNeighborA < nextNeighborB) {
                if (iteratorA.hasNext())
                    nextNeighborA = iteratorA.next();
                else
                    break;
            }

            else if (nextNeighborB < nextNeighborA) {
                if (iteratorB.hasNext())
                    nextNeighborB = iteratorB.next();
                else
                    break;
            }
        }
    }

    return commonNeighbors;
}

// ------------------------------ PRIVATE METHODS ------------------------------

private class EdgeIterator implements Iterator<int[]> {

    private int vertex;
    private int[] nextPair;
    private Iterator<Integer> neighborIterator;

    public EdgeIterator() {
        this.vertex = 0;
        this.neighborIterator = neighborLists.get(0).keySet().iterator();
        this.getNextPair();
    }

    public boolean hasNext() {
        return this.nextPair != null;
    }

    public int[] next() {
        if (this.nextPair == null)
            throw new NoSuchElementException();
        int[] temp = this.nextPair;
        this.getNextPair();
        return temp;
    }

    public void remove() {
        throw new UnsupportedOperationException();
    }

    private void getNextPair() {
        this.nextPair = null;

        while (this.nextPair == null && this.neighborIterator != null) {
            while (this.neighborIterator.hasNext()) {
                int neighbor = this.neighborIterator.next();

                if (this.vertex <= neighbor) {
                    this.nextPair = new int[] {vertex, neighbor};
                    return;
                }
            }

            this.vertex++;
            if (this.vertex < getNumVertices())
                this.neighborIterator = neighborLists.get(this.vertex).keySet().iterator();
            else
                this.neighborIterator = null;
        }   
    }
}

// ------------------------------ GETTERS & SETTERS ------------------------------

public int getNumEdges() {
    return this.numEdges;
}


public int getNumVertices() {
    return this.neighborLists.size();
}


public Double getEdgeWeight(int vertexA, int vertexB) {
    return this.neighborLists.get(vertexA).get(vertexB);
}


public Set<Integer> getNeighbors(int vertex) {
    return Collections.unmodifiableSet(this.neighborLists.get(vertex).keySet());
}


public Iterator<int[]> getEdgeIterator() {
    return new EdgeIterator();
}

}

like image 754
Zarjio Avatar asked May 22 '13 03:05

Zarjio


People also ask

What is time complexity of set in Java?

the elements in a set are sorted, but the add, remove, and contains methods has time complexity of o(log (n)).

What is the time complexity of HashSet in Java?

For HashSet, LinkedHashSet, and EnumSet, the add(), remove() and contains() operations cost constant O(1) time thanks to the internal HashMap implementation. Likewise, the TreeSet has O(log(n)) time complexity for the operations listed in the previous group.

What is complexity Big O notation of ArrayList contains () method?

Getting back to complexity analysis, the ArrayList. contains() method requires O(n) time. So the time we spend to find a specific object here depends on the number of items we have in the array.

What is the time complexity of ArrayList remove method?

The time complexity of remove(int index) Method is O(N). Reason: ArrayList implements RandomAccess interface, so accessing any random element will be done in O(1) complexity. However, since the remaining elements also need to be shifted by one place to their left, the overall time complexity becomes O(N).


2 Answers

It depends on the implementation; for example HashSet.size() simply calls size() on its internal hashMap which returns a variable;

//HashSet
public int size() {
    return map.size();
}

//Hashmap
public int size() {
    return size;
}
like image 192
Craig Avatar answered Nov 15 '22 06:11

Craig


It depends on the implementation. For example, consider SortedSet.subSet. That returns a SortedSet<E> (which is therefore a Set<E>) but I certainly wouldn't expect the size() operation on that subset to be O(1).

You haven't said what kind of set you're using, nor exactly what the calculateIntersection methods do - but if they're returning views onto existing sets, I wouldn't be at all surprised to hear that finding the size of that view is expensive.

You've talked about HashSet, TreeSet, and LinkedHashSet, all of which are O(1) for size()... but if the calculateIntersection method originally takes one of those sets and creates a view from it, that could well explain what's going on. It would help if you'd give more details - ideally a short but complete program we could use to reproduce the problem.

like image 26
Jon Skeet Avatar answered Nov 15 '22 06:11

Jon Skeet