Алгоритм для выбора одной, случайной комбинации значений?

Скажем, у меня есть y различные значения, и я хочу выбрать x из них в случайном порядке. Какой эффективный алгоритм для этого? Я мог бы просто называть rand() x раз, но производительность была бы плохой, если x, y были большими.

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

Как эффективно генерировать список K неповторяющихся целых чисел от 0 до верхней границы N охватывает этот случай для перестановок.

Ответ 1

Роберт Флойд изобрел алгоритм выборки для таких ситуаций. Он обычно превосходит перетасовку, а затем захватывает первые элементы x, поскольку он не требует хранения O (y). Как первоначально написано, он принимает значения из 1..N, но тривиально, чтобы произвести 0..N и/или использовать несмежные значения, просто обрабатывая значения, которые он выражает как индексы, в вектор/массив/независимо.

В псевдокоде алгоритм работает следующим образом (кража из колонки Jon Bentley Programming Pearls "Образец Brilliance" ).

initialize set S to empty
for J := N-M + 1 to N do
    T := RandInt(1, J)
    if T is not in S then
        insert T in S
    else
        insert J in S

Этот последний бит (вставка J, если T уже находится в S), является сложной частью. Суть в том, что обеспечивает правильную математическую вероятность вставки J, чтобы она давала непредвзятые результаты.

It O (x) 1 и O (1) относительно хранения y, O (x).

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


1 O (x 2) в худшем случае для отображения хеш-отображения, которым можно пренебречь, поскольку это практически несуществующий патологический случай, когда все значения имеют тот же хеш

Ответ 2

Предполагая, что вы хотите, чтобы заказ был случайным (или не против, чтобы это было случайным), я просто использовал бы усеченный Fisher-Yates shuffle. Запустите алгоритм перетасовки, но остановитесь, как только вы выбрали первые x значения вместо "случайного выбора" всех y из них.

Фишер-Йейтс работает следующим образом:

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

Шаги после первого не изменяют последний элемент массива. Шаги после первых двух не влияют на последние два элемента. Шаги после первого x не влияют на последние элементы x. Поэтому в этот момент вы можете остановиться - верх массива содержит равномерно случайно выбранные данные. Нижняя часть массива содержит несколько рандомизированных элементов, но перестановка, которую вы получаете из них, распределяется неравномерно.

Конечно, это означает, что вы испортили входной массив - если это означает, что вам нужно будет взять его копию перед запуском, а x мало по сравнению с y, тогда копирование всего массива не очень эффективно. Обратите внимание, что если все, что вы собираетесь использовать в будущем, это дальнейший выбор, то тот факт, что он в несколько случайном порядке не имеет значения, вы можете просто использовать его снова. Если вы делаете выбор несколько раз, следовательно, вы можете сделать только одну копию в начале и амортизировать стоимость.

Ответ 3

Если вам действительно нужно создать комбинации - где порядок элементов не имеет значения - вы можете использовать combinadics, поскольку они реализованы, например, здесь Джеймс МакКафри.

Сравните это с k-permutations, где порядок элементов имеет значение.

В первом случае (1,2,3), (1,3,2), (2,1,3), (2,3,1), (3,1,2), (3, 2,1) считаются одинаковыми - в последнем они считаются отличными, хотя они содержат одни и те же элементы.

Если вам нужны комбинации, вам может понадобиться генерировать только одно случайное число (хотя оно может быть немного большим), которое может быть использовано непосредственно для поиска m-й комбинации. Так как это случайное число представляет индекс конкретной комбинации, то ваше случайное число должно быть от 0 до C (n, k). Вычисление комбинаторов также может занять некоторое время.

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

Пока неясно, нужны ли вам комбинации или k-перестановки, вот код С# для последнего (да, мы могли бы сгенерировать только дополнение, если x > y/2, но тогда мы остались бы с комбинацию, которую нужно перетасовать, чтобы получить реальную k-перестановку):

static class TakeHelper
{
    public static IEnumerable<T> TakeRandom<T>(
        this IEnumerable<T> source, Random rng, int count)
    {
        T[] items = source.ToArray();

        count = count < items.Length ? count : items.Length;

        for (int i = items.Length - 1 ; count-- > 0; i--)
        {
            int p = rng.Next(i + 1);
            yield return items[p];
            items[p] = items[i];
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        Random rnd = new Random(Environment.TickCount);
        int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7 };
        foreach (int number in numbers.TakeRandom(rnd, 3))
        {
            Console.WriteLine(number);
        }
    }
}

Другая, более сложная реализация, которая генерирует k-перестановки, которые я лежал, и я считаю, что это улучшает существующие алгоритмы, если вам нужно только перебирать результаты. Хотя он также должен генерировать x случайных чисел, он использует только память O (min (y/2, x)):

    /// <summary>
    /// Generates unique random numbers
    /// <remarks>
    /// Worst case memory usage is O(min((emax-imin)/2, num))
    /// </remarks>
    /// </summary>
    /// <param name="random">Random source</param>
    /// <param name="imin">Inclusive lower bound</param>
    /// <param name="emax">Exclusive upper bound</param>
    /// <param name="num">Number of integers to generate</param>
    /// <returns>Sequence of unique random numbers</returns>
    public static IEnumerable<int> UniqueRandoms(
        Random random, int imin, int emax, int num)
    {
        int dictsize = num;
        long half = (emax - (long)imin + 1) / 2;
        if (half < dictsize)
            dictsize = (int)half;
        Dictionary<int, int> trans = new Dictionary<int, int>(dictsize);
        for (int i = 0; i < num; i++)
        {
            int current = imin + i;
            int r = random.Next(current, emax);
            int right;
            if (!trans.TryGetValue(r, out right))
            {
                right = r;
            }
            int left;
            if (trans.TryGetValue(current, out left))
            {
                trans.Remove(current);
            }
            else
            {
                left = current;
            }
            if (r > current)
            {
                trans[r] = left;
            }
            yield return right;
        }
    }

Общая идея состоит в том, чтобы сделать Fisher-Yates shuffle и запомнить перестановки в перестановке. Он не был опубликован нигде и не получил никакого экспертного обзора. Я считаю, что это скорее любопытство, чем практическое значение. Тем не менее я очень открыт для критики и, как правило, хотел бы знать, если вы обнаружите в этом что-то не так - подумайте об этом (и добавьте комментарий перед downvoting).

Ответ 4

Небольшое предложение: если x → y/2, то, вероятно, лучше выбрать случайные элементы y-x, затем выбрать комплементарный набор.

Ответ 5

Почему производительность была бы плохой, если бы x или y были большими? На какую производительность вы надеетесь? т.е. как вы предлагаете выбирать х элементов в случайном порядке менее чем за время O (x)?

В С++ вы можете использовать std::random_shuffle, а затем выбрать первые элементы x. std::random_shuffle использует перемешивание Fisher-Yates, упомянутое полигенными смазочными материалами.

Ответ 6

Если, например, у вас есть 2 ^ 64 различных значения, вы можете использовать алгоритм симметричного ключа (с блоком из 64 бит), чтобы быстро перетасовать все комбинации. (например, Blowfish).

for(i=0; i<x; i++)
   e[i] = encrypt(key, i)

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

Ответ 7

Фокус в том, чтобы использовать вариацию shuffle или, другими словами, частичную перетасовку.

function random_pick( a, n ) 
{
  N = len(a);
  n = min(n, N);
  picked = array_fill(0, n, 0); backup = array_fill(0, n, 0);
  // partially shuffle the array, and generate unbiased selection simultaneously
  // this is a variation on fisher-yates-knuth shuffle
  for (i=0; i<n; i++) // O(n) times
  { 
    selected = rand( 0, --N ); // unbiased sampling N * N-1 * N-2 * .. * N-n+1
    value = a[ selected ];
    a[ selected ] = a[ N ];
    a[ N ] = value;
    backup[ i ] = selected;
    picked[ i ] = value;
  }
  // restore partially shuffled input array from backup
  // optional step, if needed it can be ignored
  for (i=n-1; i>=0; i--) // O(n) times
  { 
    selected = backup[ i ];
    value = a[ N ];
    a[ N ] = a[ selected ];
    a[ selected ] = value;
    N++;
  }
  return picked;
}

ПРИМЕЧАНИЕ алгоритм строго O(n) в как время, так и пробел, дает несмещенный выбор (это частичный несмещенный перетасовка) и неразрушающего на входном массиве (в качестве частичного перетасовки будет), но это необязательно

адаптирован из здесь

Обновление

другой подход, используя только один вызов PRNG (генератор псевдослучайных чисел) в [0,1] по IVAN STOJMENOVIC, "О СЛУЧАЙНОМ И АДАПТИВНОМ ПАРАЛЛЕЛЬНОМ ПОКОЛЕНИИ КОМБИНАТОРНЫХ ОБЪЕКТОВ" (раздел 3), сложности O(n) (наихудший)

введите описание изображения здесь

Ответ 8

Вот простой способ сделать это, что является неэффективным, если Y намного больше, чем X.

void randomly_select_subset(
    int X, int Y,
    const int * inputs, int X, int * outputs
) {
    int i, r;
    for( i = 0; i < X; ++i ) outputs[i] = inputs[i];
    for( i = X; i < Y; ++i ) {
        r = rand_inclusive( 0, i+1 );
        if( r < i ) outputs[r] = inputs[i];
    }
}

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

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