5

我知道,这似乎是一个愚蠢的问题,您会期望任何集合上 size() 的时间复杂度都是 O(1) - 但我发现我的代码中的“优化”需要调用 size () 实际上是在减慢速度。

那么,Java 中 Sets 的 size() 时间复杂度是多少?

我的代码是递归算法的实现,用于在图中找到最大团(不重要)。基本上,优化只是检查两个 Set 是否具有相同的大小(这些 Set 以任何一种方式构造),并且如果它们的大小相等,则只允许再进行一次递归调用(之后停止递归)。

这是我的代码的(简化)版本:

        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);
    }
}

我注释掉的行是有问题的行 - 你可以看到他们检查集合 newCandidates 和 Candidates 的大小是否相同,如果是,则只允许递归更深一层。

取消注释行时,代码运行速度会慢 10% - 无论使用的集合是 HashSets、TreeSets 还是 LinkedHashSets,都是如此。这是没有意义的,因为这些行确保将有更少的递归调用。

我唯一可以假设的是,在集合上调用 size() 需要很长时间。在 Java 中调用 Set 的 size() 是否比 O(1) 花费更长的时间?

编辑

既然有人问了,这里是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;
}

第二次编辑 这里是完整的代码,如果你喜欢的话。我犹豫要不要发布它,因为它又长又讨厌,但是人们问了,所以这里是:

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();
}

}

4

2 回答 2

7

这取决于实施;例如HashSet.size()简单地调用size()它的内部hashMap返回一个变量;

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

//Hashmap
public int size() {
    return size;
}
于 2013-05-22T03:12:54.083 回答
4

这取决于实施。例如,考虑SortedSet.subSet. 这会返回 a SortedSet<E>(因此是 a Set<E>),但我当然不希望size()对该子集的操作是 O(1)。

你还没有说你正在使用什么样的集合,也没有确切地说这些calculateIntersection方法做了什么——但是如果他们将视图返回到现有集合中,听到发现该视图的大小是昂贵的。

您已经讨论过HashSetTreeSetLinkedHashSet,所有这些都是O(1) for size()... 但如果该calculateIntersection方法最初采用其中一个集合并从中创建一个视图,则可以很好地解释发生了什么。如果您提供更多详细信息会有所帮助 - 理想情况下,我们可以使用一个简短但完整的程序来重现问题。

于 2013-05-22T03:10:58.187 回答