给定一个列表和对应的概率列表,如何高效地从列表中随机选取一定数量的对象?
例如:
aList = [3,4,2,1,4,3,5,7,6,4]
MyProba = [0.1,0.1,0.2,0,0.1,0,0.2,0,0.2,0.1]
其中,aList
中的每个元素都有一个对应的概率值,表示该元素被选中的概率。
传统的方法是通过循环遍历列表,并根据概率值随机选择元素。然而,这种方法对于长列表和大量的抽取次数来说非常低效。
解决方案
为了提高效率,可以使用沃克别名法 (Walker’s alias method) 来进行随机选择。沃克别名法是一种预处理方法,它可以将随机选择的复杂度从 O(n) 降低到 O(1),其中 n 是列表的长度。
沃克别名法的基本原理是将列表中的元素分成两组:
- 主组:该组中的元素的概率大于或等于 0.5。
- 次组:该组中的元素的概率小于 0.5。
主组中的元素可以直接选择,而次组中的元素需要通过转换才能选择。转换过程如下:
- 将次组中的每个元素与主组中的一个元素配对。
- 将次组中元素的概率添加到与其配对的主组元素的概率中。
- 将次组中的元素从列表中删除。
最终,列表中只剩下主组中的元素,并且每个元素的概率都大于或等于 0.5。此时,就可以通过随机生成一个 0 到 1 之间的值来选择一个元素。如果生成的随机值小于等于某个元素的概率,则选择该元素;否则,继续生成随机值,直到选择到一个元素。
下面是使用沃克别名法实现随机选择的代码示例:
import random
def alias_sample(probabilities):
"""
根据概率值列表生成一个随机变量的采样函数。
参数:
probabilities: 一个列表,其中包含每个元素的概率值。
返回:
一个函数,该函数可以从给定的概率值列表中生成一个随机变量的采样值。
"""
n = len(probabilities)
# 计算主组和次组中的元素
main_group = []
secondary_group = []
for i, p in enumerate(probabilities):
if p >= 1.0:
main_group.append(i)
else:
secondary_group.append(i)
# 构建别名表
alias_table = [None] * n
for sg_element in secondary_group:
mg_element = random.choice(main_group)
alias_table[sg_element] = mg_element
probabilities[mg_element] -= (1.0 - probabilities[sg_element])
# 规范化概率值
for i in range(n):
probabilities[i] /= n
def sample():
"""
从给定的概率值列表中生成一个随机变量的采样值。
返回:
一个随机变量的采样值。
"""
# 生成一个随机数
r = random.random()
# 根据随机数选择主组或次组
if r < sum(probabilities[mg for mg in main_group]):
return random.choice(main_group)
else:
sg_index = random.choices(range(len(secondary_group)), probabilities)[0]
return alias_table[secondary_group[sg_index]]
return sample
# 测试代码
aList = [3,4,2,1,4,3,5,7,6,4]
MyProba = [0.1,0.1,0.2,0,0.1,0,0.2,0,0.2,0.1]
# 创建随机选择函数
random_sample = alias_sample(MyProba)
# 进行 10 次随机选择
nb_draws = 10
list_of_drawn_elements = []
for one_draw in range(nb_draws):
list_of_drawn_elements.append(random_sample())
print(list_of_drawn_elements)
使用沃克别名法可以大大提高随机选择的效率,特别是对于长列表和大量的抽取次数的情况。