Определение, имеет ли незаказанный вектор <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 немного больше эффективности?

37
задан Jonathan Leffler 4 May 2010 в 21:55
поделиться

11 ответов

Ваш первый пример должен быть 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();
}
27
ответ дан 27 November 2019 в 04:44
поделиться

Невозможно ли использовать контейнер, который обеспечивает такую "гарантию" с самого начала? Будет ли полезно отмечать дубликаты в момент вставки, а не в какой-то момент в будущем? Когда я хотел сделать что-то подобное, я пошел именно в этом направлении; просто используя набор в качестве "основного" контейнера, и, возможно, создавая параллельный вектор, если мне нужно сохранить первоначальный порядок, но, конечно, это делает некоторые предположения о доступности памяти и процессора...

6
ответ дан 27 November 2019 в 04:44
поделиться

В стандартной библиотеке есть 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 , как вы знаете, зависит :-).

6
ответ дан 27 November 2019 в 04:44
поделиться

Вы можете использовать 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); так же, как ваш пример. Я не думаю, что вы теоретически можете гарантировать, что это будет быстрее, хотя использование C ++ 0x std :: unordered_set вместо std :: set сделает это за ожидаемое линейное время. - но для этого требуется, чтобы ваши элементы были хешируемыми, а также были определены operator == , что может быть не так просто.

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

2
ответ дан 27 November 2019 в 04:44
поделиться

Ну, ваш первый должен принимать только 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)) наихудший случай, а средний случай зависит от распределения входных данных.

1
ответ дан 27 November 2019 в 04:44
поделиться

Если тип T, который вы храните в вашем векторе, велик и его копирование требует больших затрат, подумайте о создании вектора указателей или итераторов на элементы вашего вектора. Отсортируйте его на основе элемента, на который указывают, а затем проверьте на уникальность.

Вы также можете использовать для этого std::set. Шаблон выглядит так

template <class Key,class Traits=less<Key>,class Allocator=allocator<Key> > class set

Я думаю, вы можете предоставить соответствующий параметр Traits и вставить необработанные указатели для скорости или реализовать простой класс-обертку для указателей с оператором <.

Не используйте конструктор для вставки в набор. Используйте метод insert. Метод (одна из перегрузок) имеет сигнатуру

pair <iterator, bool> insert(const value_type& _Val);

Проверяя результат (второй член), вы часто можете обнаружить дубликат гораздо быстрее, чем если бы вы вставляли все элементы.

1
ответ дан 27 November 2019 в 04:44
поделиться

В (очень) частном случае сортировки дискретных значений с известным, не слишком большим максимальным значением 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).

1
ответ дан 27 November 2019 в 04:44
поделиться

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

0
ответ дан 27 November 2019 в 04:44
поделиться

Могу я добавить свои 2 цента.

Прежде всего, как заметил @Potatoswatter , если ваши элементы не дешевы для копирования (встроенные / небольшие POD), вы захотите использовать указатели на исходные элементы, а не копировать их.

Во-вторых, доступны 2 стратегии.

  1. Просто убедитесь, что не вставлены дубликаты. Это, конечно, означает управление вставкой, что обычно достигается путем создания специального класса (с вектором в качестве атрибута).
  2. Всякий раз, когда это свойство необходимо, проверяйте его на наличие дубликатов.

Должен признать, я склоняюсь к первому. Инкапсуляция, четкое разделение ответственности и все такое.

В любом случае, есть несколько способов, в зависимости от требований. Первый вопрос:

  • должны ли мы размещать элементы в векторе в определенном порядке или мы можем «возиться» с ними?

Если мы можем возиться с ними, я бы посоветовал сохранение сортировки вектора : 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? Поскольку асимптотическая сложность является спорным вопросом при работе с несколькими сотнями ....
  • И, конечно же: можете ли вы обеспечить единство во время вставки? Можете ли вы изменить сам вектор?
2
ответ дан 27 November 2019 в 04:44
поделиться

Для начала вы могли бы объединить преимущества обоих: прекратить строить множество, если вы уже обнаружили дубликат:

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 вместо этого.


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

6
ответ дан 27 November 2019 в 04:44
поделиться

Необходимо отсортировать вектор, если необходимо быстро определить, есть ли в нем только уникальные элементы. В противном случае лучшее, что вы можете сделать, это O(n^2) среда выполнения или O(n log n) с пространством O(n). Я думаю, что лучше всего написать функцию, которая предполагает, что входные данные отсортированы.

template<class Fwd>
bool is_unique(In first, In last)
{
    return adjacent_find(first, last) == last;
}

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

10
ответ дан 27 November 2019 в 04:44
поделиться
Другие вопросы по тегам:

Похожие вопросы: