Какова временная и пространственная сложность метода retainAll при использовании на HashSets в Java?

например, в приведенном ниже коде:

public int commonTwo(String[] a, String[] b)
{
    Set common = new HashSet<String>(Arrays.asList(a));
    common.retainAll(new HashSet<String>(Arrays.asList(b)));
    return common.size();
} 

2 ответов


давайте внимательно рассмотрим код. Метод retainAll - это наследство от AbstractCollection и (по крайней мере, в OpenJDK) выглядит так:

public boolean retainAll(Collection<?> c) {
    boolean modified = false;
    Iterator<E> it = iterator();
    while (it.hasNext()) {
        if (!c.contains(it.next())) {
            it.remove();
            modified = true;
        }
    }
    return modified;
}

есть один большой это отметить здесь, мы петля над this.iterator() и звонок c.contains. Таким образом, сложность времени n звонки c.contains здесь n = this.size() и не более n звонки it.remove().

это важно, что contains метод вызывается на другое Collection и так сложность зависит от сложности другого Collection contains.

Итак, в то время как:

Set<String> common = new HashSet<>(Arrays.asList(a));
common.retainAll(new HashSet<>(Arrays.asList(b)));

будет O(a.length), as HashSet.contains и HashSet.remove как O(1) (амортизируется).

если вы

common.retainAll(Arrays.asList(b));

тогда из-за O(n) contains on Arrays.ArrayList это стало бы O(a.length * b.length) - т. е. расходы O(n) копирование массива в HashSet вы на самом деле принять вызов retainAll гораздо быстрее.

As что касается сложности пространства, то нет дополнительного пространства (за пределами Iterator) требуется retainAll, но ваш вызов на самом деле довольно дорогой космический, поскольку вы выделяете два новых HashSet реализаций, которые на самом деле являются полноценными HashMap.

можно отметить еще две вещи:

  1. нет причин выделять HashSet с элементами a - более дешевая коллекция, которая также имеет O(1) удалить из середины, такие как LinkedList можете использовать. (дешевле в памяти, а также время сборки - хэш-таблица не строится)
  2. ваши изменения теряются при создании новых экземпляров коллекции и возвращаются только b.size().

реализация может быть найдена в java.util.AbstractCollection класса. Способ его реализации выглядит следующим образом:

public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        boolean modified = false;
        Iterator<E> it = iterator();
        while (it.hasNext()) {
            if (!c.contains(it.next())) {
                it.remove();
                modified = true;
            }
        }
        return modified;
    }

таким образом, он будет повторять все в вашем common установите и проверьте, содержит ли этот элемент коллекция, переданная в качестве параметра.

в вашем случае оба HashSets, таким образом, это будет O(n), так как содержит должно быть O(1) амортизируется и повторяется над вашим common set - O (n).

одно улучшение, которое вы можете сделать, просто не копировать a в новый HashSet, потому что он будет использоваться в любом случае вы можете сохранить список.