комбинация и перестановка в C ++ - PullRequest
32 голосов
/ 06 февраля 2010

Какова наиболее широко используемая из существующих в C ++ библиотека для предоставления всей комбинации и перестановки k элементов из n элементов?

Я задаю не алгоритм, а существующую библиотеку или методы.

Спасибо.

Ответы [ 4 ]

36 голосов
/ 13 марта 2011

Я решил проверить решения Дмана и Чарльза Бейли здесь.Я назову их решениями A и B соответственно.Мой тест - посещение каждой комбинации размером vector<int> = 100, 5 одновременно.Вот код теста:

Код теста

struct F
{
    unsigned long long count_;

    F() : count_(0) {}

    bool operator()(std::vector<int>::iterator, std::vector<int>::iterator)
    {++count_; return false;}
};

int main()
{
    typedef std::chrono::high_resolution_clock Clock;
    typedef std::chrono::duration<double> sec;
    typedef std::chrono::duration<double, std::nano> ns;
    int n = 100;
    std::vector<int> v(n);
    std::iota(v.begin(), v.end(), 0);
    std::vector<int>::iterator r = v.begin() + 5;
    F f;
    Clock::time_point t0 = Clock::now();
    do
    {
        f(v.begin(), r);
    } while (next_combination(v.begin(), r, v.end()));
    Clock::time_point t1 = Clock::now();
    sec s0 = t1 - t0;
    ns pvt0 = s0 / f.count_;
    std::cout << "N = " << v.size() << ", r = " << r-v.begin()
              << ", visits = " << f.count_ << '\n'
              << "\tnext_combination total = " << s0.count() << " seconds\n"
              << "\tnext_combination per visit = " << pvt0.count() << " ns";
}

Весь код был скомпилирован с использованием clang ++ -O3 на Intel Core i5 с частотой 2,8 ГГц.

Решение A

Решение A приводит к бесконечному циклу.Даже когда я делаю n очень маленьким, эта программа никогда не завершается.Впоследствии по этой причине проголосовали.

Решение B

Это редактирование.Решение B изменилось в процессе написания этого ответа.Сначала он давал неправильные ответы, а из-за очень быстрого обновления теперь дает правильные ответы.Он печатает:

N = 100, r = 5, visits = 75287520
    next_combination total = 4519.84 seconds
    next_combination per visit = 60034.3 ns

Решение C

Далее я попробовал решение из N2639 , которое выглядит очень похоже на решение A, но работаетправильно.Я назову это решение C, и оно напечатает:

N = 100, r = 5, visits = 75287520
    next_combination total = 6.42602 seconds
    next_combination per visit = 85.3531 ns

Решение C в 703 раза быстрее, чем решение B.

Решение D

Наконец, решение D найдено здесь .Это решение имеет другую сигнатуру / стиль и называется for_each_combination и используется так же, как std::for_each.Приведенный выше код драйвера изменяется между вызовами таймера следующим образом:

Clock::time_point t0 = Clock::now();
f = for_each_combination(v.begin(), r, v.end(), f);
Clock::time_point t1 = Clock::now();

Решение D выводит на печать:

N = 100, r = 5, visits = 75287520
    for_each_combination = 0.498979 seconds
    for_each_combination per visit = 6.62765 ns

Решение D в 12,9 раз быстрее, чем решение C, и в 9000 раз быстреечем решение B.

Я считаю, что это относительно небольшая проблема: только 75 миллионов посещений.По мере того как количество посещений увеличивается до миллиардов, расхождение в производительности между этими алгоритмами продолжает расти.Решение B уже громоздко.Решение C в конечном итоге становится громоздким.Решение D - это самый эффективный алгоритм для посещения всех известных мне комбинаций.

Ссылка , показывающая решение D , также содержит несколько других алгоритмов для перечисления и посещения перестановок с различными свойствами (циклический,обратимый и т. д.).Каждый из этих алгоритмов был разработан с производительностью в качестве одной из целей.И обратите внимание, что ни один из этих алгоритмов не требует, чтобы начальная последовательность была в отсортированном порядке.Элементы не должны быть даже LessThanComparable.

19 голосов
/ 06 февраля 2010

Комбинации: из статьи Марка Нельсона по той же теме мы имеем next_combination http://marknelson.us/2002/03/01/next-permutation Перестановки: из STL мы имеем std :: next_permutation

   template <typename Iterator>
   inline bool next_combination(const Iterator first, Iterator k, const Iterator last)
   {
      if ((first == last) || (first == k) || (last == k))
         return false;
      Iterator itr1 = first;
      Iterator itr2 = last;
      ++itr1;
      if (last == itr1)
         return false;
      itr1 = last;
      --itr1;
      itr1 = k;
      --itr2;
      while (first != itr1)
      {
         if (*--itr1 < *itr2)
         {
            Iterator j = k;
            while (!(*itr1 < *j)) ++j;
            std::iter_swap(itr1,j);
            ++itr1;
            ++j;
            itr2 = k;
            std::rotate(itr1,j,last);
            while (last != j)
            {
               ++j;
               ++itr2;
            }
            std::rotate(k,itr2,last);
            return true;
         }
      }
      std::rotate(first,k,last);
      return false;
   }
17 голосов
/ 11 апреля 2010

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

Стандартная библиотека имеет std::next_permutation, и вы можете тривиально построить next_k_permutation из нее и next_combination из этого.

template<class RandIt, class Compare>
bool next_k_permutation(RandIt first, RandIt mid, RandIt last, Compare comp)
{
    std::sort(mid, last, std::tr1::bind(comp, std::tr1::placeholders::_2
                                            , std::tr1::placeholders::_1));
    return std::next_permutation(first, last, comp);
}

Если у вас нет tr1::bind или boost::bind, вам нужно создать функциональный объект, который поменяет местами аргументы для данного сравнения. Конечно, если вас интересует только std::less вариант next_combination, вы можете использовать std::greater напрямую:

template<class RandIt>
bool next_k_permutation(RandIt first, RandIt mid, RandIt last)
{
    typedef typename std::iterator_traits<RandIt>::value_type value_type;

    std::sort(mid, last, std::greater< value_type >());
    return std::next_permutation(first, last);
}

Это относительно безопасная версия next_combination. Если вы можете гарантировать, что диапазон [mid, last) находится в том же порядке, что и после вызова на next_combination, тогда вы можете использовать более простое:

template<class BiDiIt, class Compare>
bool next_k_permutation(BiDiIt first, BiDiIt mid, BiDiIt last, Compare comp)
{
    std::reverse(mid, last);
    return std::next_permutation(first, last, comp);
}

Это также работает как с двунаправленными итераторами, так и с итераторами с произвольным доступом.

Чтобы вывести комбинации вместо k-перестановок, мы должны убедиться, что мы выводим каждую комбинацию только один раз, поэтому мы будем возвращать комбинацию только в том случае, если это k-перестановка в порядке.

template<class BiDiIt, class Compare>
bool next_combination(BiDiIt first, BiDiIt mid, BiDiIt last, Compare comp)
{
    bool result;
    do
    {
        result = next_k_permutation(first, mid, last, comp);
    } while (std::adjacent_find( first, mid,
                             std::tr1::bind(comp, std::tr1::placeholders::_2
                                                , std::tr1::placeholders::_1) )
                                                                        != mid );
    return result;
}

В качестве альтернативы можно было бы использовать обратный итератор вместо вызова параметра bind или явно использовать std::greater, если используется std::less для сравнения.

2 голосов
/ 06 января 2011

@ Чарльз Бейли выше:

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

4 выберите 2 примера:
12 34
12 43 (после сортировки)
13 24 (после следующей перестановки)
13 42 (после сортировки)
14 23 (после следующей_перестановки)
14 32 (после сортировки)
21 34 (после следующей_перестановки)

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

Не полностью проверено, только краткие тесты ..

<code>
template
bool next_combination(RandIt first, RandIt mid, RandIt last)
{
    typedef typename std::iterator_traits< RandIt >::value_type value_type;
    std::sort(mid, last, std::greater< value_type >() );
    while(std::next_permutation(first, last)){
        if(std::adjacent_find(first, mid, std::greater< value_type >() ) == mid){
            return true;
        }
        std::sort(mid, last, std::greater< value_type >() );
    return false;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...