【问题标题】:Randomly selecting an element from a weighted list从加权列表中随机选择一个元素
【发布时间】:2011-05-29 12:29:56
【问题描述】:

我有一个包含 100,000 个对象的列表。每个列表元素都有一个与之关联的“权重”,它是一个从 1 到 N 的正整数。

从列表中选择随机元素最有效的方法是什么?我希望我的随机选择元素的分布与列表中的权重分布相同。

例如,如果我有一个列表 L = {1,1,2,5},我希望平均有 5/9 的时间选择第 4 个元素。

假设插入和删除在此列表中很常见,因此任何使用“积分区域表”的方法都需要经常更新 - 希望有一个需要 O(1) 运行时间和 O(1) 额外内存的解决方案。

【问题讨论】:

  • @user470379 这是不同的,因为权重是 1、2、...、N。
  • @user470379,我相信支持插入和删除的要求可以区分它。
  • @marcog 不,它们不是:L = {1,1,2,5},我希望在 5/9 的时间内选择第四个元素
  • @Alin 我认为你可能是对的。 @John你能确认吗?暂时删除我的答案。

标签: algorithm list random statistics


【解决方案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)中生成一个随机数,并在累积和数组中对其进行二进制搜索。记录 b[i]x 的位置 i 并返回 a[i]。因此,如果随机数是 3,我们会得到 i=3,并且会返回 a[3]=2。这确保了与增强树解决方案相同的复杂性,但实现更简单。

【讨论】:

  • 您需要 BST,因为除了对元素进行采样之外,该问题还需要能够添加和删除元素。
  • 啊,根本没注意到 - 不错的解决方案!
【解决方案3】:

在 O(n) 中运行的解决方案是从选择第一个元素开始。然后对于每个后续元素,要么保留您拥有的元素,要么将其替换为下一个元素。令 w 为目前考虑的元素的所有权重之和。然后以概率 w/(w+x) 保留旧的,并以 p=x/(w+x) 选择新的,其中 x 是下一个元素的权重。

【讨论】:

  • 是的,这就是我现在所做的。我觉得应该有一些巧妙的优化,以避免每次都查看所有元素。 100,000 是很多。
  • 例如,您可以保持列表排序,然后在查找时在某些情况下可以向前跳转多个元素。或者建立一个分区系统,什么的。
【解决方案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)AND 您使用随机访问数据结构(列表意味着 O(n) 访问时间),那么它可以快速完成:

    1) 选择一个随机元素 (O(1))。由于在这一步有1/num_elems 的机会选择一个元素,它允许我们在步骤2) 中使用num_elems* 提升,从而加速算法。

    2) 计算其预期概率:num_elems * (weight/total_weight)

    3) 取一个 0..1 范围内的随机数,如果它小于预期的概率,你就有了输出。如果没有,请从步骤 1 开始重复)

    【讨论】:

    • 我不是反对者,但问题是步骤 2) 中的乘积可能大于 1。溢出意味着高权重元素不会像应有的那样频繁返回。
    • @antonakos:是的,但这可以解决。该算法的优点在于它可以比 O(log(n)) 更快。
    • O(N)。大 O 的意思是“最坏的情况”。
    • @JohnShedletsky 8 年多之后,我的挑剔......我知道很有帮助。但大 O 是衡量标准,它可能是最坏的情况,但不一定。
    • 大 O 是最坏情况执行时间的渐近下界。从字面上看,它被定义为最坏的情况。找出你更了解的东西来挑剔。
    猜你喜欢
    • 2011-11-14
    • 2023-01-11
    • 2011-01-09
    • 2014-02-07
    • 2012-07-31
    • 2013-10-19
    • 1970-01-01
    • 2017-05-11
    • 2021-12-30
    相关资源
    最近更新 更多