Каков наиболее эффективный способ копирования элементов, которые встречаются только один раз в 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
Для меньших чисел разница все еще сохраняется, пока не станет некритическим кодом