Алгоритм случайного нахождения всех комбинаций из k элементов из стека из n

У меня проблема, похожая на описанную здесь:

Алгоритм возврата всех комбинаций k элементов из n

Я ищу что-то подобное, которое охватывает все возможные комбинации k из n. Однако мне нужно, чтобы подмножество сильно отличалось от нарисованного ранее. Например, если бы мне нужно было нарисовать подмножество из 3 элементов из набора из 8, следующий алгоритм мне не пригодился бы, поскольку каждое подмножество очень похоже на ранее нарисованное:

11100000, 11010000, 10110000, 01110000, ...

Я ищу алгоритм, который выбирает подмножества более «случайным» образом, т.е. где большинство элементов в одном подмножестве не используется повторно в следующем:

11100000, 00010011, 00101100, ...

Кто-нибудь знает такой алгоритм?

Я надеюсь, что мой вопрос имел смысл, и что кто-то может мне помочь =)

С уважением,

христианин


person Community    schedule 11.05.2009    source источник


Ответы (6)


Как насчет того, чтобы сначала сгенерировать все возможные комбинации k из n, а затем переставить их с помощью случайной функции.

Если у вас есть результат в векторе, выполните цикл по вектору: пусть для каждого элемента он поменяется местами с элементом в случайной позиции.

Это, конечно, становится медленным для больших k и n.

person AnnaR    schedule 11.05.2009
comment
Дело в том, что я работаю с очень большими k и n, настолько большими, что создание и запоминание всех возможных комбинаций k из n не вариант. Поэтому подмножества необходимо вычислять на ходу. Алгоритм, с которым я сейчас работаю, случайным образом извлекает подмножества с помощью ГСЧ. Это работает, но генерация псевдослучайных чисел требует некоторых вычислений + есть небольшой шанс выбрать один и тот же элемент дважды. Я подумал, что, возможно, я мог бы ускорить процесс, используя вместо этого генератор комбинаций, но, как я уже говорил выше, должны быть большие различия от подмножества к подмножеству. - person ; 11.05.2009

Это не совсем случайно, но в зависимости от ваших потребностей это может вам подойти.

  1. Подсчитайте количество возможных комбинаций. Назовем их Н.
  2. Вычислите большое число, взаимно простое с N. Назовем его P.
  3. Упорядочите комбинации и дайте им номера от 1 до N. Назовем их от C1 до CN
  4. Итерация для выходных комбинаций. Первый будет VP mod N, второй будет C2*P mod N, третий C3*P mod N sub> и т. д. По сути, Outputi = Ci*P mod N. Mod подразумевается как оператор модуля.

Если P выбрать тщательно, вы получите кажущиеся случайными комбинации. Значения, близкие к 1 или к N, будут давать мало отличающиеся значения. Лучше выбирать значения, близкие, скажем, к N/4 или N/5. Вы также можете рандомизировать генерацию P для каждой необходимой вам итерации.

person Vilx-    schedule 11.05.2009
comment
Это сработало бы, если бы мне не нужно было предварительно вычислять и сохранять все возможные подмножества. Но я попытаюсь найти генератор комбинаций, который позволит мне вычислить n-е подмножество без необходимости вычислять все предшествующие ему n-1 подмножеств. Спасибо за помощь =) - person ; 11.05.2009
comment
Ты подтолкнул меня на это. Кстати, шаг 3, на мой взгляд, неявный: именно это и делает использование бегущего счетчика. - person Raphaël Saint-Pierre; 11.05.2009
comment
@Christian: на самом деле вам не нужно генерировать комбинации n-1, чтобы получить n-ю. Найдите функцию деранжирования для алгоритма минимального изменения. Все, что я смог найти на данный момент, это: jjj.de/fxt/#fxt. Я опубликую что-нибудь более краткое сегодня вечером, когда найду свою реализацию. - person Raphaël Saint-Pierre; 11.05.2009
comment
Да, изначально у меня тоже не было Шага 3, но потом я понял, что мне нужно давать имена комбинациям, чтобы иметь возможность ссылаться на них позже. - person Vilx-; 11.05.2009

В продолжение моего комментария к этот ответ, вот некоторый код, который позволяет определить состав подмножества по его "индексу" в порядке колекса. Бессовестно украденный из моих собственных заданий.

//////////////////////////////////////
// NChooseK
//
// computes    n!
//          --------
//          k!(n-k)!
//
// using Pascal's identity
// i.e. (n,k) = (n-1,k-1) + (n-1,k)
//
// easily optimizable by memoization
long long NChooseK(int n, int k)
{
    if(k >= 0 && k <= n && n >= 1)
    {
        if( k > n / 2)
            k = n - k;
        if(k == 0 || n == 0)
            return 1;
        else
            return NChooseK(n-1, k-1) + NChooseK(n-1, k);
    } 
    else
        return 0;
}

///////////////////////////////////////////////////////////////////////
// SubsetColexUnrank
//  The unranking works by finding each element
//  in turn, beginning with the biggest, leftmost one.
//  We just have to find, for each element, how many subsets there are
//  before the one beginning with the elements we have already found.
//
// It stores its results (indices of the elements present in the subset) into T, in ascending order.
void SubsetColexUnrank(long long r, int * T, int subsetSize)
{
    assert( subsetSize >= 1 );

    // For each element in the k-subset to be found
    for(int i = subsetSize; i >= 1; i--)
    {
        // T[i] cannot be less than i
        int x = i;

        // Find the smallest element such that, of all the k-subsets that contain it,
        // none has a rank that exceeds r.            
        while( NChooseK(x, i) <= r )
            x++;

        // update T with the newly found element
        T[i] = x;

        // if the subset we have to find is not the first one containing this element
        if(r > 0)
        {
            // finding the next element of our k-subset
            // is like finding the first one of the same subset
            // divided by {T[i]}
            r -= NChooseK(x - 1, i);
        }
    }
}

Случайный вход, случайный выход.

Порядок колекса таков, что его функция неранжирования не нуждается в размере набора, из которого выбираются элементы для работы; предполагается, что количество элементов равно NChooseK (размер набора, размер подмножества).

person Raphaël Saint-Pierre    schedule 12.05.2009

Как насчет случайного выбора k элементов. т.е. выбрать p-й, где p является случайным между 1 и n, затем переупорядочить то, что осталось и выбрать q-й, где q находится между 1 и n-1 и т.д.?

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

person Community    schedule 11.05.2009
comment
Как я уже сказал AnnaR, на самом деле я пытаюсь ускорить процесс, который в настоящее время использует ГСЧ. Кроме того, k и n слишком велики для предварительного расчета и запоминания всех комбинаций. Поэтому я подумал, что, возможно, генератор комбинаций, вычисляющий подмножества на ходу, мог бы решить проблему. Однако в каждом подмножестве должны быть существенные различия. Такого алгоритма может не быть, но я подумал, что могу попробовать и спросить здесь =) - person ; 11.05.2009

Под «случайным поиском», я думаю, вы имеете в виду лексикографически отдаленный. Это относится к комбинации i против i-1 или i против всех предыдущих комбинаций?

Если да, то вот несколько предложений:

  • поскольку большинство комбинаторов дают упорядоченный вывод, есть два варианта:

    1. design or find a generator which somehow yields non-ordered output
    2. перечислить и сохранить достаточно/все комбинации в связанном файле массива/db
  • если вы решите пойти с дверью № 2, то вы можете просто получить доступ к случайно упорядоченным комбинациям случайными целыми числами от 1 до # комбинаций.

  • В качестве окончательной проверки сравните текущую и предыдущую комбинации, используя меру разницы/расстояния между комбинациями, например. для беззнакового Bit::Vector в Perl:

    $vec1->Lexicompare($vec2) >= $MIN_LEX_DIST

  • Вы можете еще раз заглянуть за дверь №1, так как даже при умеренных значениях n и k можно получить большой массив:

C(n,k) = n!/(k!(n-k)!)

РЕДАКТИРОВАТЬ:

Только что увидел ваш комментарий к AnnK ... может быть, лексическое сравнение все еще может помочь вам пропустить похожие комбинации?

person bubaker    schedule 11.05.2009
comment
Я имею в виду лексикографически далекий, да. И это касается всех предыдущих комбинаций. Я знаю, что выбор большего количества комбинаций уменьшает максимально возможное лексикографическое расстояние для следующего подмножества. И я не требую, чтобы подмножество было настолько лексикографически удалено от предыдущих подмножеств, насколько это возможно. Однако расстояние должно быть больше минимального. Я начинаю думать, что это невозможно сделать без использования ГСЧ, от чего я и пытаюсь уйти. Кроме того, как я уже говорил, мои n и k слишком велики для хранения всех комбинаций. - person ; 11.05.2009

В зависимости от того, что вы пытаетесь сделать, вы можете сделать что-то вроде игры в карты. Держите два списка: Source — ваш исходный (неиспользуемый) список; и Used второй - это "уже выбранный" список. Когда вы случайным образом выбираете k элементов из Source, вы перемещаете их в свой Used список.

Если в Source осталось k элементов, когда вам нужно снова выбрать, вы выбираете их все и меняете списки местами. Если элементов меньше k, вы выбираете j элементов из Used и добавляете их в Source, чтобы получить k элементов в Source, затем выбираете их все и меняете списки.

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

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

Хорошо то, что вам не нужно беспокоиться о предварительном вычислении всех подмножеств, а ваши требования к памяти линейны с вашими данными (2 списка размером n).

person Erich Mirabal    schedule 12.05.2009