Каков наиболее эффективный способ копирования элементов, которые встречаются только один раз в std-векторе?

У меня есть std-вектор с такими элементами:

[0 , 1 , 2 , 0 , 2 , 1 , 0 , 0 , 188 , 220 , 0 , 1 , 2 ]

Каков наиболее эффективный способ найти и скопировать элементы, которые встречаются только один раз в этом векторе, исключая алгоритм грубой силы O (n ^ 2)? В этом случае новый список должен содержать [188, 220]

Ответы

Ответ 1

  • Сделайте unordered_map<DataType, Count> count;
  • Итерации по вектору ввода, увеличивающему количество каждого значения. Сортировка count[value]++;
  • Итерации по клавишам копирования count, для которых значение равно 1.

Это O(n). У вас есть хэши, поэтому для небольших наборов данных нормальная карта может быть более эффективной, но технически это будет O(n log n).

Это хороший метод для дискретных наборов данных.

Пример кода:

#include <iostream>
#include <unordered_map>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    vector<int> v{1,1,2,3,3,4};
    unordered_map<int,int> count;
    for (const auto& e : v) count[e]++;
    vector<int> once;
    for (const auto& e : count) if(e.second == 1) once.push_back(e.first);
    for (const auto& e : once) cout << e << '\n';
    return 0;
}

Я пробовал несколько идей. Но я не вижу пути вокруг map. unordered_multiset - это отличный способ... кроме того, что он не позволяет вам перебирать ключи. У него есть метод проверки количества ключей, но вам нужен другой набор только для ключей для зондирования. Я не считаю это более простым способом. В современном С++ с auto подсчет прост. Я также просмотрел библиотеку algorithm, но я не нашел никаких transfrom, copy_if, generate и т.д., Которые могли условно преобразовать элемент (запись в карте → значение, если count равно 1).

Ответ 2

Существует очень мало универсально-оптимальных алгоритмов. Какой алгоритм работает лучше всего, обычно зависит от свойств обрабатываемых данных. Одним из таких примеров является удаление дубликатов.

Является ли v малым и заполняется в основном уникальными значениями?

auto lo = v.begin(), hi = v.end();
std::sort(lo, hi);
while (lo != v.end()) {
    hi = std::mismatch(lo + 1, v.end(), lo).first;
    lo = (std::distance(lo, hi) == 1) ? hi : v.erase(lo, hi);
}

Является ли v маленьким и заполненным в основном дубликатами?

auto lo = v.begin(), hi = v.end();
std::sort(lo, hi);
while (lo != v.end()) {
    hi = std::upper_bound(lo + 1, v.end(), *lo);
    lo = (std::distance(lo, hi) == 1) ? hi : v.erase(lo, hi);
}

Является ли v гигантским?

std::unordered_map<int, bool> keyUniqueness{};
keyUniqueness.reserve(v.size());
for (int key : v) {
    bool wasMissing = keyUniqueness.find(key) == keyUniqueness.end();
    keyUniqueness[key] = wasMissing;
}
v.clear();
for (const auto& element : keyUniqueness) {
    if (element.second) { v.push_back(element.first); }
}

И так далее.

Ответ 3

Ответ

@luk32 - это, безусловно, самый эффективный способ решения этого вопроса. Однако, если у вас мало памяти и вы не можете позволить себе unordered_map, есть и другие способы сделать это.

Вы можете использовать std::sort() для сортировки вектора сначала. Затем не дубликаты можно найти на одной итерации. Общая сложность O(nlogn).

Если вопрос немного отличается, и вы знаете, что существует только один не дублирующий элемент, вы можете использовать этот код (код в Java). Сложность здесь O(n).

Ответ 4

Поскольку вы используете std::vector, я предполагаю, что вы хотите максимизировать все его преимущества, включая ссылочную локальность. Для этого нам нужно немного ввести текст. И я сравнил код ниже...

У меня есть линейный O(n) алгоритм здесь (эффективно O(nlog(n))), его бит напоминает ответ брайана, но вместо этого я использую OutputIterators делать это на месте. Предварительное условие состоит в том, что оно сортируется.


template<typename InputIterator, typename OutputIterator>
OutputIterator single_unique_copy(InputIterator first, InputIterator last, OutputIterator result){
    auto previous = first;
    if(previous == last || ++first == last) return result;
    while(true){
        if(*first == *previous)
            while((++first != last) && (*first == *previous));
        else
            *(result++) = *previous;
        if(first == last) break;
        previous = first;
        ++first;
    }
    return ++result;
}

И вот пример использования:

int main(){
    std::vector<int> vm = {0, 1, 2, 0, 2, 1, 0, 0, 1, 88, 220, 0, 1, 2, 227, -8};
    std::vector<int> kk;
    std::sort(vm.begin(), vm.end());
    single_unique_copy(vm.begin(), vm.end(), std::back_inserter(kk));
    for(auto x : kk) std::cout << x << ' ';
    return 0;
}

Как и ожидалось, выход:

-8, 88, 220, 227

Ваш прецедент может отличаться от моего, поэтому сначала профиль...: -)

EDIT:

  • Использование luk32-алгоритма и моего... Использование 13 миллионов элементов... созданный в порядке убывания, повторяется при каждом i % 5.
  • В сборке отладки luk32: 9.34 секунды и мой: 7.80 секунды
  • Под -O3, luk32: 2.71 секунды и мой 0.52 секунды
  • Mingw5.1 64bit, Windows10, 1.73Ghz Core i5 4210U, 6 ГБ DDR3 1600Mhz RAM
  • Здесь вы можете найти здесь http://coliru.stacked-crooked.com/a/187e5e3841439742

Для меньших чисел разница все еще сохраняется, пока не станет некритическим кодом