Самый быстрый способ найти объединение множеств

У меня есть наборы пар int like set<pair<int,int> > x1, x2, ... xn (n может быть между 2 и 20). Какой самый быстрый способ найти объединение этих множеств ?

Извините, если я не был ясно в начале, я имел в виду быстро в производительности, выделение памяти не проблема.

7 ответов


к сожалению, я считаю, что вы ограничены в линейную O(N) решение, так как все объединение было бы комбинацией элементов в обоих наборах.

template<typename S>
S union_sets(const S& s1, const S& s2)
{
     S result = s1;

     result.insert(s2.cbegin(), s2.cend());

     return result;
}

предполагая, что результат тоже должен быть набором, тогда у вас нет выбора, кроме как вставить каждый элемент каждого x_i в результирующий набор. Таким образом, очевидная реализация:

set<pair<int,int>> x(x1);
x.insert(x2.begin(), x2.end());
// etc

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

один элемент insert принимает position намек, что если правильно ускоряет ввод. Так это может оказывается, что что-то вроде этого быстрее, чем x.insert(x2.begin(), x2.end());:

auto pos = x.begin()
for (auto it = x2.begin(); it != x2.end(); ++it) {
    pos = x.insert(pos, *it);
}

это зависит от данных, хотя эта позиция может или не может быть точным. Вы можете убедиться, что это, поставив все элементы в порядке, прежде чем начать, для которого лучший инструмент, вероятно,set_union. Это лучше назвать merge_and_dedupe_sorted_ranges, потому что это не имеет ничего общего с std::set. Вы могли бы либо set_union в промежуточные векторы или в такие множества:

set<pair<int,int>> x;
set_union(x1.begin(), x1.end(), x2.begin(), x2.end(), inserter(x, x.end());

моя озабоченность использованием set_union заключается в том, что в чтобы получить преимущество добавления элементов в набор в порядке возрастания, вам нужно создавать новый пустой контейнер каждый раз, когда вы его вызываете (потому что, если он не пуст, то добавленные элементы должны чередоваться со значениями уже в нем). Накладные расходы этих контейнеров могут быть выше, чем накладные расходы на вставку в набор в произвольном порядке: вам придется протестировать его.


сначала найдите объединение наименьших множеств. То есть упорядочите свои наборы по длине набора, вычислите объединение двух наименьших наборов, удалите эти наборы, вставьте объединение в свой список наборов в соответствии с его размером.

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

Edit: и для каждого объединения операция между двумя наборами - объединить меньшие в большие.


Я предполагаю, что с быстро ты имеешь в виду быстро реализовать.

затем: std:: set_union (*)

пример для двух наборов:

#include <set>
#include <algorithm>
#include <iterator>
using namespace std;

int main () {
    set<pair<int,int> > a, b, uni;
    set_union (a.begin(), a.end(),
               b.begin(), b.end(),
               inserter(uni, uni.begin()));

}

для n наборов рукописное написание может быть самым доступным решением:

#include <set>
#include <vector>
using namespace std;

int main () {
    vector<set<pair<int,int>>> sets;
    set<pair<int,int>> uni;

    for (const auto &s : sets)
        for (const auto &elem : s)
            uni.insert (elem);
}

хотя в целом следует отдавать предпочтение стандартным алгоритмам и получать прибыль от их качественной реализации.

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


(*) Примечание: сайт хмурится иногда за то, что не является 100% точным по сравнению со стандартом


попробуйте set_union в алгоритме заголовка.


можно использовать std:: set_union рекурсивно или просто вставьте все наборы в результирующий набор (повторяющиеся элементы устраняются набором). Если количество элементов очень мало, вы можете попробовать вставить все это в вектор, отсортировать его и использовать std:: unique на вектор.


чтобы сэкономить на выделении памяти и улучшить локальность, было бы лучше использовать один vector<T> как рабочая память.

построить vector<T> и зарезервировать общее количество элементов во всех s (подсчет дубликатов). Затем, начиная с пустого диапазона [v.begin(), v.begin()), расширьте его до набора (уникального, отсортированного) диапазона, добавив содержимое каждого набора, объединяя и uniquifying:

vector<T> v;
v.reserve(<total size>);
for (set<T> &s: sets) {
    auto middle = v.insert(v.end(), s.begin(), s.end());
    inplace_merge(v.begin(), middle, v.end());
    v.erase(v.unique(v.begin(), v.end()), v.end());
}