Можно ли написать функцию, подобную next_permutation, но которая переставляет только r значений вместо n?

std::next_permutation (и std::prev_permutation) переставляет все значения в диапазоне [first, last), всего n! перестановки (при условии, что все элементы уникальны).

можно ли написать такую ​​функцию:

template<class Iter>
bool next_permutation(Iter first, Iter last, Iter choice_last);

Это переставляет элементы в диапазоне [first, last), но выбирает только элементы в диапазоне [first, choice_last). т.е. у нас есть, может быть, 20 элементов, и мы хотим перебрать все перестановки 10 вариантов из них, 20 P 10 вариантов против 20 P 20.

  • Iter — это итератор с произвольным доступом для моих целей, но если его можно реализовать как двунаправленный итератор, то отлично!
  • Чем меньше нужно внешней памяти, тем лучше, но для моих целей это не имеет значения.
  • Выбранные элементы на каждой итерации вводятся в первые элементы последовательности.

Возможна ли реализация такой функции? Кто-нибудь знает какие-либо существующие реализации?

Вот, по сути, что я делаю, чтобы взломать это. Предложения о том, как улучшить это, также приветствуются.

  • Начните с вектора V из N элементов, из которых я хочу посетить каждую перестановку R элементов, выбранных из него (R <= N).
  • Создайте вектор I длины R со значениями { 0, 1, 2, ... R - 1 }, который будет служить индексом для элементов V.
  • На каждой итерации построить вектор C длины R со значениями { V[I[0]], V[I[1]], ... V[I[R - 1]] }
  • Сделайте что-нибудь со значениями в C.
  • Примените функцию для перестановки элементов I и повторите итерацию, если это возможно.

Эта функция выглядит так:

bool NextPermutationIndices(std::vector<int> &I, int N)
{
    const int R = I.size();
    for (int i = R - 1; ; --i) {
        if (I[i] < N - R + i) {
            ++I[i];
            return true;
        }

        if (i == 0)
            return false;

        if (I[i] > I[i-1] + 1) {
            ++I[i-1];
            for (int j = i; j < R; ++j)
                I[j] = I[j-1] + 1;
            return true;
        }
    }
}

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


ИЗМЕНИТЬ:

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

template<class BidirectionalIterator>
bool next_partial_permutation(BidirectionalIterator first,
                              BidirectionalIterator middle,
                              BidirectionalIterator last)
{
    std::reverse(middle, last);
    return std::next_permutation(first, last);
}

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


person Greg Rogers    schedule 26.10.2008    source источник
comment
Если вы имеете в виду то, что, как вы думаете, я имею в виду - выбор любых R элементов из N - сигнатура вашего метода должна быть bool next_permutation(Iter first, Iter last, int result_count), чтобы сделать намерение более ясным.   -  person David Schmitt    schedule 26.10.2008
comment
Что означает перестановка элементов в диапазоне [первый, последний), но только выбор элементов в диапазоне [первый, выбор_последний)? Вы просто выбираете любые элементы R из N и переставляете эти элементы R? Или вы действительно имеете в виду элементы в этом диапазоне (и если да, то что происходит с остальными?)   -  person ShreevatsaR    schedule 26.10.2008
comment
Ввод целого числа означает, что оно работает только с итераторами произвольного доступа. Я надеялся избежать такого ограничения, если это возможно.   -  person Greg Rogers    schedule 27.10.2008


Ответы (4)


Для перебора перестановок nPk я использовал алгоритм for_each_permutation(), представленный в этой старой статье CUJ. . Он использует хороший алгоритм Кнута, который вращает элементы на месте, оставляя их в исходном порядке в конце. Таким образом, он соответствует вашим требованиям к внешней памяти. Это также работает для двунаправленных итераторов. Это не соответствует вашему требованию выглядеть как next_permutation(). Однако я думаю, что это победа — мне не нравятся API с отслеживанием состояния.

person fizzer    schedule 26.10.2008
comment
Отличная ссылка, хотя она вводит в заблуждение тем, что для реализации действительно требуется RandomAccessIterator, а не BidirectionalIterator для операции Iter + int. Хотя они не дают этого понять. - person Greg Rogers; 26.10.2008
comment
Приношу свои извинения - давно не смотрел. Возможно, advance() это исправит. - person fizzer; 26.10.2008

Исходный код генератора комбинаций Java находится по адресу http://www.merriampark.com/comb.htm. Удалите идиомы Java, и это почти то, что вы ищете, реализованное в виде генератора, чтобы контролировать использование вашей памяти.


Эта задача относится к области математики, известной как Комбинаторика, которая является частью Дискретной математики. Дискретная математика имеет решающее значение для практиков в области компьютерных наук, поскольку она включает почти всю математику, которую мы используем ежедневно (например, логику, алгоритмы, счет, отношения, теорию графов и т. д.). Я настоятельно рекомендую Дискретная и комбинаторная математика: прикладное введение или Дискретная математика и его приложения, если вы можете себе это позволить.

(Примечание: этот вопрос связан с "алгоритмом группировки", но не совсем повторяется, поскольку этот вопрос просит решить ее в общем случае.)

person Jacob Krall    schedule 26.10.2008
comment
Хорошая ссылка. По сути, это тот подход, который я использую, хотя я мог бы устранить некоторые потери, упаковав часть кода в класс вместо того, что я делаю для каждого использования. Нет ли способа сделать это, заставив его проверять и манипулировать фактическим массивом данных? - person Greg Rogers; 26.10.2008

Алгоритмическое упрощение состояло бы в том, чтобы разделить это на два отдельных шага.

  • Создайте список всех возможных вариантов выбора элементов R из исходных данных.
  • Для каждого из этих вариантов создайте все возможные перестановки выбранных элементов.

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

Выбор можно реализовать на двунаправленном итераторе, пропуская невыбранные элементы. Сгенерируйте все выборки, например. путем перестановки последовательности из R единиц и (N-R) нулей. Это потребует O(N) дополнительной памяти, но позволит вам переставить исходную последовательность на месте.

person David Schmitt    schedule 26.10.2008

Что бы это ни стоило, вот реализация, которая работает.

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

Одно из преимуществ этого способа над этот ответ заключается в том, что таким образом не используются перестановки в лексикографическом порядке, что может (но, вероятно, не) быть важным. Также неудобно иногда использовать boost::bind для создания функтора для передачи for_each.

template<class Iter>
bool next_choice_permutation(Iter first, Iter choice, Iter last)
{
    if (first == choice)
        return false;

    Iter i = choice;
    --i;
    if (*i < *choice) {
        std::rotate(i, choice, last);
        return true;
    }

    while (i != first) {
        Iter j = i;
        ++j;
        std::rotate(i, j, last);
        --i;
        --j;
        for (; j != last; ++j) {
            if (*i < *j)
                break;
        }
        if (j != last) {
            std::iter_swap(i, j);
            return true;
        }
    }
    std::rotate(first, ++Iter(first), last);
    return false;
}
person Greg Rogers    schedule 26.10.2008