我想讨论如何以最佳方式实施以下内容:
给定一组元素,
所有这些都是截然不同的。调用此输入集N
,其大小为n
。这些元素各有一个重量。现在,您需要创建此R
大小为N
的子集(r
),其中r
小于或等于n
。
此子集同样不能包含重复。 R
的每个元素都需要从N
中随机选择,如果元素具有更高的权重,则选择它的机会需要更高。如果N
中所有元素的总权重为W
,那么选择元素i
的机会需要为w_i/W
。
最后一个细节是权重很容易发生变化,但只是增加,它们不会减少。
我想要一个简洁的实现。我在Java工作,但我希望找到一些有趣的语言属性或细节(甚至伪代码)。
现在,对于我自己的解决方案:我创建原始元素集的Array List
。我确保权重是自然数,如果元素的权重是n
,我会添加n
次。然后我随机播放arraylist(collections.shuffle
)并继续从洗牌列表中取出元素并将它们添加到Java Set
,直到集合的大小为r
。如果元素的权重增加,则会向原始数组列表添加更多次。再次洗牌,制作新的子集。
我的实现需要大量的内存,如果集合变大,那么随机播放也会变慢。那里有更好的主意吗?
答案 0 :(得分:1)
首先,让我们将其简化为只绘制一个元素,然后计算
sum[-1] = 0
sum[i] = sum[i-1] + weight[i]
然后,您只需在范围r
中绘制一个数字[0,sum)
,然后对r
进行二分搜索。它所涉及的范围是您绘制的数字。
这是O(n)
时间解决方案。
显然,您可以为更多元素执行此操作,但您必须删除从集合中选择的元素,或重复直到您选择新项目。然而,对于大的子集大小,两种解决方案都会衰减为二次复杂度:(
但是,我们可以改进它以做得更好吗?
是。使用二叉搜索树而不是数组。二叉搜索树实际上是order statistics tree的变体,而不是存储#children(v)
,而是存储每个子树中的权重总和。除此之外 - 它基本上与订单统计树保持一致。
有关树解决方案的更多信息可以作为类似问题的答案找到:Algorithm to shuffle an Array randomly based on different weights
构建树的复杂性为O(nlogn)
,每个查询+删除都为O(logn)
,这会为您提供O(nlogn + klogn) = O(nlogn)
所以,我们有两个选择:
如果k
o(logn)
(little o此处)O(n)
,则更愿意为每个查询重新创建一个O(nlogn)
时间的数组。否则,您应该更喜欢(在时间复杂度方面)O(1)
基于树的解决方案。
就空间而言,两种解决方案都需要额外空间的线性。
这可以通过一次通过更好地完成。这称为weighted reservoir sampling。它的主要缺点是由于指数部分导致的大权重的数字问题造成的不稳定性(至少根据我的经验)。
此解决方案以线性时间运行,具有k
额外空间(如果不包括大小为git_libgit2_init()
的输出数组)。