【问题标题】:What is the Time Complexity of size() for Sets in Java?Java 中 Set 的 size() 的时间复杂度是多少?
【发布时间】:2013-05-22 03:07:02
【问题描述】:

我知道,这似乎是一个愚蠢的问题,您会期望任何集合上 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 中调用 Sets 的 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();
}

}

【问题讨论】:

  • HashSets 只是返回一个变量。所以它的 O(1)
  • 有些可疑。无论您在calculateIntersection() 中使用何种 Set 实现,上面的代码都不会因为 2 个 size() 调用而慢 10%。一定还有其他你没有展示的东西。
  • 我已经发布了所有(相关的)代码。它又长又讨厌,我认为它不会澄清任何事情。
  • 当 SO 上的人要求提供代码来重现问题时,他们通常(总是?)想要一个 SSCCE,它有问题,整个问题,只有问题
  • 在将 nextCandidate 添加到 notCandidates 并将其从图表中删除之前,您的优化会返回。由于没有其他任何东西修改 notCandidates 或图形,递归中的前一个调用者有更高的机会找到 notCandidates.set.isEmpty(),这会导致对象分配和 addAll() - 这肯定是 not 一个 O(1) 操作。那么这足以多占 10% 的时间吗?我不知道基线是什么(例如 100 毫秒 --> 110 毫秒或 60 秒 --> 66 秒?)。

标签: java size set hashset treeset


【解决方案1】:

这取决于实现;例如HashSet.size() 只是在其内部的hashMap 上调用size(),它返回一个变量;

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

//Hashmap
public int size() {
    return size;
}

【讨论】:

  • 我会接受克雷格的回答,因为它在技术上是正确的,尽管我现在意识到这是一个可怕的问题,希望它会被删除:/
【解决方案2】:

这取决于实现。例如,考虑SortedSet.subSet。这会返回一个 SortedSet&lt;E&gt;(因此是 Set&lt;E&gt;),但我当然不希望该子集上的 size() 操作为 O(1)。

你没有说你使用的是什么类型的集合,也没有确切地说 calculateIntersection 方法做了什么 - 但如果他们将视图返回到现有集合,听到这个发现我一点也不感到惊讶该视图的大小很昂贵。

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

【讨论】:

  • @SotiriosDelimanolis:是的,我错过了 - 但我怀疑还有更多。将编辑。
  • Jon - 我已经用 calculateIntersection() 的实现更新了这个问题 - 它只是返回另一个 Set 与进入其中的两个集合类型相同。
  • @Zarjio:该代码无效。 Set 是一个接口。提供无法编译的代码是没有意义的……我们想要一个完整的示例,我们可以用它来重现问题。
  • 是的,我已经为该构造函数尝试了 TreeSet、HashSet 和 LinkedHashSet,并且都遭受了相同的性能下降。
  • @Zarjio:您不必发布完整的程序 - 只需一个完整的程序来演示问题。我们仍然没有main 方法...实际上我们无法运行。顺便说一句,您是否做过任何诊断以确定条件是否真正得到满足?
猜你喜欢
  • 2010-10-26
  • 1970-01-01
  • 2012-02-13
  • 2013-05-21
  • 2011-02-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多