Определение неупорядоченного вектора <T> имеет все уникальные элементы
Профилирование моего кода, связанного с процессором, подсказывает, что я долгое время проверяю, содержит ли контейнер полностью уникальные элементы. Предполагая, что у меня есть большой контейнер с несортированными элементами (с <
и =
), у меня есть две идеи о том, как это можно сделать:
Первое использование набора:
template <class T>
bool is_unique(vector<T> X) {
set<T> Y(X.begin(), X.end());
return X.size() == Y.size();
}
Второй цикл по элементам:
template <class T>
bool is_unique2(vector<T> X) {
typename vector<T>::iterator i,j;
for(i=X.begin();i!=X.end();++i) {
for(j=i+1;j!=X.end();++j) {
if(*i == *j) return 0;
}
}
return 1;
}
Я тестировал их как можно лучше, и из того, что я могу собрать, прочитав документацию о STL, ответ (как обычно) зависит от этого. Я думаю, что в первом случае, если все элементы уникальны, это очень быстро, но если есть большое вырождение, операция, по-видимому, занимает время O (N ^ 2). Для вложенного подхода итератора противоположное кажется правдивым, оно светится быстро, если X[0]==X[1]
, но принимает (понятно) время O (N ^ 2), если все элементы уникальны.
Есть ли лучший способ сделать это, возможно, алгоритм STL, построенный для этой цели? Если нет, есть ли какие-либо предложения eek из более высокой эффективности?
Ответы
Ответ 1
Ваш первый пример должен быть O (N log N), так как set
занимает log N времени для каждой вставки. Я не думаю, что возможно более быстрое O.
Второй пример, очевидно, O (N ^ 2). Коэффициент и использование памяти низки, поэтому в некоторых случаях это может быть быстрее (или даже быстрее).
Это зависит от того, что T
, но для общей производительности я бы рекомендовал сортировать вектор указателей на объекты.
template< class T >
bool dereference_less( T const *l, T const *r )
{ return *l < *r; }
template <class T>
bool is_unique(vector<T> const &x) {
vector< T const * > vp;
vp.reserve( x.size() );
for ( size_t i = 0; i < x.size(); ++ i ) vp.push_back( &x[i] );
sort( vp.begin(), vp.end(), ptr_fun( &dereference_less<T> ) ); // O(N log N)
return adjacent_find( vp.begin(), vp.end(),
not2( ptr_fun( &dereference_less<T> ) ) ) // "opposite functor"
== vp.end(); // if no adjacent pair (vp_n,vp_n+1) has *vp_n < *vp_n+1
}
или в стиле STL,
template <class I>
bool is_unique(I first, I last) {
typedef typename iterator_traits<I>::value_type T;
…
И если вы можете изменить порядок исходного вектора, конечно,
template <class T>
bool is_unique(vector<T> &x) {
sort( x.begin(), x.end() ); // O(N log N)
return adjacent_find( x.begin(), x.end() ) == x.end();
}
Ответ 2
Вы должны отсортировать вектор, если хотите быстро определить, имеет ли он только уникальные элементы. В противном случае лучшее, что вы можете сделать, это O (n ^ 2) runtime или O (n log n) runtime с O (n) пространством. Я думаю, что лучше всего написать функцию, предполагающую сортировку ввода.
template<class Fwd>
bool is_unique(In first, In last)
{
return adjacent_find(first, last) == last;
}
затем попросите клиента отсортировать вектор или сделать отсортированную копию вектора. Это откроет дверь для динамического программирования. То есть, если клиент отсортировал вектор в прошлом, тогда у них есть возможность сохранить и ссылаться на этот отсортированный вектор, чтобы они могли повторить эту операцию для O (n) времени исполнения.
Ответ 3
Стандартная библиотека имеет std::unique
, но это потребует, чтобы вы сделали копию всего контейнера (обратите внимание, что в обоих ваших примерах вы также делаете копию всего вектора, так как вы без необходимости передаете вектор по стоимость).
template <typename T>
bool is_unique(std::vector<T> vec)
{
std::sort(vec.begin(), vec.end());
return std::unique(vec.begin(), vec.end()) == vec.end();
}
Будет ли это быстрее, чем использование std::set
, как вы знаете, зависит: -).
Ответ 4
Нельзя ли использовать контейнер, который предоставляет эту "гарантию" из-за ошибки? Было бы полезно отметить дубликат во время ввода, а не в какой-то момент в будущем? Когда я хотел сделать что-то подобное, это направление, в котором я ушел; просто используя набор как "первичный" контейнер и, возможно, создавая параллельный вектор, если мне нужно поддерживать исходный порядок, но, конечно, это делает некоторые предположения о доступности памяти и процессора...
Ответ 5
С одной стороны, вы могли бы объединить преимущества обоих: прекратить создание набора, если вы уже обнаружили дубликат:
template <class T>
bool is_unique(const std::vector<T>& vec)
{
std::set<T> test;
for (typename std::vector<T>::const_iterator it = vec.begin(); it != vec.end(); ++it) {
if (!test.insert(*it).second) {
return false;
}
}
return true;
}
BTW, Potatoswatter дает хорошее представление о том, что в общем случае вы можете избежать копирования T, и в этом случае вместо этого вы можете использовать std::set<const T*, dereference_less>
.
Конечно, вы могли бы сделать гораздо лучше, если бы не общий. Например, если у вас есть вектор целых чисел известного диапазона, вы можете просто отметить в массиве (или даже битете), если существует элемент.
Ответ 6
Вы можете использовать std::unique
, но для этого сначала нужно отсортировать диапазон:
template <class T>
bool is_unique(vector<T> X) {
std::sort(X.begin(), X.end());
return std::unique(X.begin(), X.end()) == X.end();
}
std::unique
изменяет последовательность и возвращает итератор в конец уникального набора, поэтому, если это еще конец вектора, тогда он должен быть уникальным.
Это выполняется в nlog (n); так же, как ваш пример. Я не думаю, что теоретически вы можете сделать это быстрее, хотя использование С++ 0x std::unordered_set
вместо std::set
будет делать это в ожидаемое линейное время, но это требует, чтобы ваши элементы были хешируемыми, а также иметь operator ==
, что может быть не так просто.
Кроме того, если вы не изменяете вектор в своих примерах, вы должны повысить производительность, передав его по ссылке const, поэтому вы не делаете ненужной копии.
Ответ 7
Если я могу добавить свои собственные 2 цента.
Прежде всего, как заметил @Potatoswatter
, если ваши элементы не являются дешевыми для копирования (встроенные/небольшие POD), вы захотите использовать указатели на исходные элементы, а не копировать их.
Во-вторых, есть 2 доступных стратегии.
- Просто убедитесь, что дубликат не вставлен в первую очередь. Это означает, конечно, управление вложением, которое обычно достигается путем создания выделенного класса (с атрибутом vector как атрибутом).
- Всякий раз, когда это свойство необходимо, проверьте наличие дубликатов
Я должен признать, что склоняюсь к первому. Инкапсуляция, четкое разделение обязанностей и все такое.
Во всяком случае, в зависимости от требований существует несколько способов. Первый вопрос:
- Нужно ли нам оставлять элементы в
vector
в определенном порядке или мы можем "испортить" их?
Если мы сможем с ними столкнуться, я бы предложил сохранить отсортированный vector
: Loki::AssocVector
, чтобы вы начали.
Если нет, то нам нужно сохранить индекс в структуре, чтобы обеспечить это свойство... подождать минуту: Boost.MultiIndex
на помощь?
В-третьих: когда вы заметили, что простой линейный поиск удваивается, в среднем получается сложность O (N 2), которая не является хорошей.
Если <
уже определено, то сортировка очевидна, с его сложностью O (N log N).
Возможно также стоит сделать T
Hashable, потому что std::tr1::hash_set
может дать лучшее время (я знаю, вам нужен RandomAccessIterator, но если T
является Hashable, то легко иметь T*
Hashable to; ))
Но в конце концов, реальная проблема заключается в том, что наши рекомендации необходимы, потому что нам не хватает данных.
- Что такое
T
, вы предполагаете, что алгоритм является общим?
- Каково количество элементов? 10, 100, 10.000, 1.000.000? Поскольку асимптотическая сложность является разнородной, когда речь идет о нескольких сотнях....
- И, конечно же: можете ли вы обеспечить единство во время вставки? Можете ли вы изменить сам вектор?
Ответ 8
Ну, ваш первый должен взять только N log(N)
, поэтому он явно лучший худший сценарий для этого приложения.
Тем не менее, вы должны иметь возможность получить лучший лучший вариант, если вы проверяете, как вы добавляете вещи в набор:
template <class T>
bool is_unique3(vector<T> X) {
set<T> Y;
typename vector<T>::const_iterator i;
for(i=X.begin(); i!=X.end(); ++i) {
if (Y.find(*i) != Y.end()) {
return false;
}
Y.insert(*i);
}
return true;
}
Это должно быть O(1)
лучший случай, O(N log(N))
худший случай, а средний случай зависит от распределения входов.
Ответ 9
Если тип T, который вы храните в своем векторе, большой, и копирование является дорогостоящим, подумайте о создании вектора указателей или итераторов для ваших векторных элементов. Сортируйте его на основе указанного элемента, а затем проверьте его уникальность.
Вы также можете использовать для этого std:: set. Шаблон выглядит следующим образом
template <class Key,class Traits=less<Key>,class Allocator=allocator<Key> > class set
Я думаю, вы можете предоставить соответствующий параметр Traits и вставить исходные указатели для скорости или реализовать простой класс-оболочку для указателей с < Оператор.
Не используйте конструктор для вставки в набор. Используйте метод вставки. Метод (один из перегрузок) имеет подпись
pair <iterator, bool> insert(const value_type& _Val);
Проверяя результат (второй элемент), вы можете часто обнаруживать дубликат намного быстрее, чем если бы вы вставили все элементы.
Ответ 10
В (очень) специальном случае сортировки дискретных значений с известным, не слишком большим максимальным значением N.
Вы должны иметь возможность запустить сортировку ведра и просто проверить, что количество значений в каждом ковше ниже 2.
bool is_unique(const vector<int>& X, int N)
{
vector<int> buckets(N,0);
typename vector<int>::const_iterator i;
for(i = X.begin(); i != X.end(); ++i)
if(++buckets[*i] > 1)
return false;
return true;
}
Сложность этого будет O (n).
Ответ 11
Используя текущие стандартные контейнеры С++, у вас есть хорошее решение в первом примере. Но если вы можете использовать хэш-контейнер, вы можете сделать лучше, так как хэш-набор будет nO (1) вместо nO (log n) для стандартного набора. Конечно, все будет зависеть от размера n и конкретной реализации библиотеки.