Случайный выбор элемента из взвешенного списка
У меня есть список из 100 000 объектов. У каждого элемента списка есть связанный с ним "вес", который является положительным int от 1 до N.
Каков наиболее эффективный способ выбора случайного элемента из списка? Я хочу, чтобы мое распределение случайно выбранных элементов было таким же, как распределение весов в списке.
Например, если у меня есть список L = {1,1,2,5}, я хочу, чтобы 4-й элемент был выбран в среднем 5/9 секунд.
Предположим, что в этом списке распространены вставки и удаления, поэтому любой подход, использующий "таблицы интегральной области", должен часто обновляться - надеясь, что существует решение с O (1) временем выполнения и требуемой дополнительной памятью O (1).
Ответы
Ответ 1
Вы можете использовать расширенное двоичное дерево поиска для хранения элементов вместе с суммой весов в каждом поддереве. Это позволяет вам вставлять и удалять элементы и веса, но вы хотите. Для выборки и обновления требуется время O (lg n) за операцию, а использование пространства - O (n).
Сэмплирование выполняется путем генерации случайного целого числа в [1, S], где S - сумма всех весов (S хранится в корне дерева) и выполнения двоичного поиска с использованием сумм взвешивания, хранящихся для каждого поддерево.
Ответ 2
Мне очень нравится решение jonderry, но мне интересно, нужна ли эта проблема такой сложной структуре, как расширенное двоичное дерево поиска. Что, если бы мы сохранили два массива, один с входными весами, скажем a = {1,1,2,5} и один с кумулятивными весами (очень похожая идея на решение jonderry), которая была бы b = {1,2,4, 9}. Теперь создадим случайное число в [1 9] (скажем, x) и бинарный поиск для него в совокупном массиве сумм. Место i, где b [i] <= x и b [i-1] > x отмечено, и возвращается [i]. Итак, если случайное число равно 3, мы получим я = 3, и будет возвращено значение [3] = 2. Это обеспечивает ту же сложность, что и расширенное дерево с более простой реализацией.
Ответ 3
Решение, которое выполняется в O (n), должно начинаться с выбора первого элемента. Затем для каждого следующего элемента либо сохраните элемент, который у вас есть, либо замените его на следующий. Пусть w - сумма всех весов для элементов, рассмотренных до сих пор. Затем сохраните старый с вероятностью w/(w + x) и выберем новый с p = x/(w + x), где x - вес следующего элемента.
Ответ 4
Это то, что я сделал для его решения:
def rchoose(list1, weights):
'''
list1 : list of elements you're picking from.
weights : list of weights. Has to be in the same order as the
elements of list1. It can be given as the number of counts
or as a probability.
'''
import numpy as np
# normalizing the weights list
w_sum = sum(weights)
weights_normalized = []
for w in weights:
weights_normalized.append(w/w_sum)
# sorting the normalized weights and the desired list simultaneously
weights_normalized, list1 = zip(*sorted(zip(weights_normalized, list1)))
# bringing the sorted tuples back to being lists
weights_normalized = list(weights_normalized)
list1 = list(list1)
# finalizing the weight normalization
dummy = []; count = 0
for item in weights_normalized:
count += item
dummy.append(count)
weights_normalized = dummy
# testing which interval the uniform random number falls in
random_number = np.random.uniform(0, 1)
for idx, w in enumerate(weights_normalized[:-1]):
if random_number <= w:
return list1[idx]
return list1[-1]
Ответ 5
Если вы знаете сумму весов (в вашем случае 9) И, вы используете структуру данных с произвольным доступом (список подразумевает время доступа O (n)), то это можно сделать быстро
1) выберите случайный элемент (O (1)). Поскольку существует шанс 1/num_elems
выбрать элемент на этом шаге, это позволяет нам использовать ускорение num_elems*
для шага 2), тем самым ускоряя алгоритм.
2) вычислить его ожидаемую вероятность: num_elems * (weight/total_weight)
3) возьмем случайное число в диапазоне 0..1, и если оно меньше ожидаемой вероятности, у вас есть выход. Если нет, повторите с шага 1)