O(1) Структура данных "Создать, найти, объединить в непересекающиеся множества"

Сегодня у меня была дискуссия с кем-то об алгоритме Kruskal Minimum Spanning Tree из-за страницы 13 этого слайда.

Автор презентации сказал, что если мы реализуем непересекающиеся множества, используя (дважды) связанный список, производительность для Make и Find будет O(1) и O(1) соответственно. Время операции Union(u,v) равно min (nu, nv), где nu и nv - размеры наборов, хранящих u и v.

Я сказал, что мы можем улучшить время, в течение которого Union(u,v) будет равно O(1), сделав указатель представления каждого члена, указывающий локатор, который содержит указатель на реальное представление множества.

В Java структура данных будет выглядеть так:

class DisjointSet {
    LinkedList<Vertex> list = new LinkedList<Vertex>(); // for holding the members, we might need it for print

    static Member makeSet(Vertex v) {
        Member m = new Member();

        DisjointSet set = new DisjointSet();
        m.set = set;
        set.list.add(m);

        m.vertex = v;

        Locator loc = new Locator();
        loc.representation = m;
        m.locator = loc;

        return m;
    }
}

class Member {
    DisjointSet set;
    Locator locator;
    Vertex vertex;

    Member find() {
        return locator.representation;
    }

    void union(Member u, Member v) { // assume nv is less than nu
        u.set.list.append(v.set.list); // hypothetical method, append a list in O(1)
        v.set = u.set;
        v.locator.representation = u.locator.representation;
    }

}

class Locator {
    Member representation;
}

Извините за минималистичный код. Если это можно сделать таким образом, то время выполнения для каждой непересекающейся операции набора (Make, Find, Union) будет O(1). Но тот, с кем я обсуждал, не видит улучшения. Я хотел бы узнать ваше мнение по этому поводу.

А также, какова самая быстрая производительность Find/Union в различных реализациях? Я не эксперт в области структуры данных, но благодаря быстрому просмотру в Интернете я обнаружил, что для этого не существует структуры данных или алгоритма с постоянным временем.

2 ответа

Моя интуиция согласна с вашим коллегой. Ты говоришь:

u.set.list.append(v.set.list); // hypothetical method, append a list in O(1)

Похоже, ваше намерение состоит в том, что объединение осуществляется через приложение. Но, чтобы внедрить Union, вам нужно будет удалить дубликаты, чтобы результат был набором. Так что я вижу алгоритм O(1) для фиксированного размера набора, например...

Int32 set1;
Int32 set2;

Int32 unionSets1And2 = set1 | set2;

Но это кажется мне обманом. Если вы делаете это для общих случаев N, я не вижу, как вы избегаете какой-либо формы итерации (или поиска хеша). И это сделало бы это O(n) (или в лучшем случае O(log n)).

К вашему сведению: мне было трудно следить за вашим кодом. В makeSet вы создаете локальный локатор, который никогда не выходит за пределы функции. Не похоже, что это что-то делает. И не ясно, каково ваше намерение в приложении. Возможно, захотите отредактировать и уточнить свой подход.

Используя версию Терьяна структуры Union-Find (со сжатием пути и взвешенным по рангу объединением), последовательность m Finds и (n-1) смешанных объединений будет в O(m.α(m,n)), где α (m, n) - обратная функция Аккермана, которая для всех практических значений m и n имеет значение 4. Таким образом, это в основном означает, что Union-Find имеет амортизированные постоянные операции в худшем случае, но не совсем.

Насколько мне известно, невозможно получить лучшую теоретическую сложность, хотя улучшения привели к повышению практической эффективности.

Для особых случаев непересекающихся множеств, таких как те, которые используются в теории языка, было показано, что линейные (т.е. все в O(1)) адаптации возможны - по существу, путем группировки узлов вместе - но эти улучшения не могут быть переведено на общую проблему. С другой стороны спектра, несколько схожая основная идея была использована с большим успехом и изобретательностью для создания алгоритма O(n) для минимального остовного дерева (алгоритм Шазеля).

Таким образом, ваш код не может быть правильным. Ошибка - то, на что указал Морон: когда вы объединяете два набора, вы обновляете только "представление" опережения каждого списка, но не всех других элементов - одновременно допуская в функции поиска, что каждый элемент напрямую знает свое представление.

Другие вопросы по тегам