Получение случайного элемента в списке, связанном в одном направлении, путем однократного обхода

#algorithm #random #traversal

#алгоритм #Случайный #обход

Вопрос:

У меня есть список, связанный в одном направлении, без знания его размера.

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

Каков алгоритм решения этой проблемы? Спасибо!

Ответ №1:

Это просто выборка из резервуара с резервуаром размером 1.

По сути, это действительно просто

  1. Выберите первый элемент независимо (для списка длиной 1 первый элемент всегда является образцом).
  2. Для каждого другого элемента с вероятностью 1 / n, где n — количество элементов, наблюдаемых на данный момент, вы заменяете уже выбранный элемент текущим элементом, на котором вы находитесь.

Выборка выполняется равномерно, поскольку вероятность выбора любого элемента в конце дня равна 1 / n (упражнение для читателя).

Комментарии:

1. @Giacomon Есть причина, по которой вы считаете, что это не сработает для небольших коллекций. Я понял, что вопрос заключается в том, чтобы предоставить единый алгоритм выборки в режиме онлайн, я думаю, это подходит просто отлично

2. @Aurojit: Я думаю, Джакомо просто говорит, что это решение подходит как для больших, так и для маленьких коллекций.

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

4. Это действительно вероятность 1 / n? Возьмите последний элемент: у него будет шанс выбора 1/2 (на последнем шаге), а также у остальных элементов сумма равна 1/2. Мне это не кажется правильным.

5. @iuliux Почему вероятность этого равна 1/2. Сам элемент имеет вероятность выбора 1 / n. Если он не выбран, любой выбранный элемент выживет с вероятностью (n — 1) / n . (1 / n-1) = 1 / n. Следовательно, одинаковая вероятность.

Ответ №2:

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

Если вам нужно собрать k элементов из любого массива с элементами n, так что вероятность каждого собранного элемента должна быть одинаковой (k / n), вы выполняете два шага,

1) Сохраните первые k элементов в хранилище. 2) Когда следующий элемент (k 1) поступает из потока, очевидно, что у вас больше нет места в вашей коллекции.Сгенерируйте случайное число от o до n, если сгенерированное случайное число меньше k, предположим, l, замените storage[l] элементом (k 1) из stream.

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

Ответ №3:

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

В википедии есть понятный алгоритм, который я цитирую ниже:

 array R[k];    // result
integer i, j;

// fill the reservoir array
for each i in 1 to k do
    R[i] := S[i]
done;

// replace elements with gradually decreasing probability
for each i in k 1 to length(S) do
    j := random(1, i);   // important: inclusive range
    if j <= k then
        R[j] := S[i]
    fi
done
  

Для вопроса требуется только 1 значение, поэтому мы принимаем k = 1.

Реализация на C :

https://ideone.com/txnsas

Ответ №4:

Это самый простой способ, который я нашел, он отлично работает и понятен:

 public int findrandom(Node start) {
    Node curr = start;
    int count = 1, result = 0, probability;
    Random rand = new Random();

    while (curr != null) {
        probability = rand.nextInt(count)   1;
        if (count == probability)
            result = curr.data;
        count  ;
        curr = curr.next;
    }
    return resu<
}