Efficiently selecting a set of random elements from a linked list
假设我有一个长度为
如何最有效地编写一个从列表中返回 有一种非常好的,有效的算法,可以使用称为储层采样的方法。 首先,我向您介绍它的历史: Knuth在p上将此算法称为R。他的1997年版的Seminumerical Algorithms(计算机编程艺术的第2卷)第144页,并为此提供了一些代码。 Knuth将算法归因于Alan G. Waterman。尽管进行了冗长的搜索,但我仍然找不到Waterman的原始文档(如果存在),这也许就是为什么您经常看到引用Knuth作为该算法的来源的原因。 McLeod和Bellhouse,1983年(1)提供了比Knuth更为全面的讨论,以及该算法有效的第一个公开证明(据我所知)。 Vitter 1985(2)回顾了算法R,然后提出了另外三种算法,它们提供相同的输出,但又有所不同。他的算法不是选择包含还是跳过每个传入元素,而是预先确定要跳过的传入元素的数量。在他的测试中(现在已经过时了),这避免了随机数的产生和每个传入数字的比较,从而大大减少了执行时间。 用伪代码算法是:
请注意,我专门编写了代码,以避免指定输入的大小。那是该算法的一个很酷的特性:您可以运行它而无需事先知道输入的大小,并且它仍然可以确保您遇到的每个元素都有以 为什么这样做? McLeod和Bellhouse(1983)使用组合数学提供了证明。很漂亮,但是在这里重建它有点困难。因此,我生成了一个更容易解释的替代证明。 我们通过归纳证明进行。
假设我们要生成一组
假设我们当前的每个
根据算法的定义,我们选择概率为
已经成为结果集一部分的每个元素都有被替换的概率
因此,将
因此,
该算法的定义告诉我们,前 参考文献 McLeod,A。Ian和David R. Bellhouse。"一种用于绘制简单随机样本的便捷算法。"皇家统计学会杂志。系列C(应用统计)32.2(1983):182-184。 (链接) Vitter,JeffreyS。"使用水库进行随机采样。" ACM Transactions on Mathematical Software(TOMS)11.1(1985):37-57。 (链接) 这称为水库采样问题。一种简单的解决方案是为列表中的每个元素分配一个随机数,如您所见,然后按照随机数的顺序保留前(或后)k个元素。 我建议:首先找到您的k个随机数。对它们进行排序。然后遍历链接列表和您的随机数一次。 如果您不知道链表的长度(如何?),则可以将第一个k捕获到数组中,然后对于节点r,在[0,r)中生成一个随机数,如果小于大于k,请替换数组的第r个项目。 (不完全相信这不会产生偏见...) 除此之外:"如果我是你,我不会从这里开始。"您确定链接列表适合您的问题吗?是否没有更好的数据结构,例如好的旧式平面数组列表。
如果您不知道列表的长度,则必须遍历整个列表以确保随机选择。我在这种情况下使用的方法是Tom Hawtin(54070)描述的方法。在遍历列表时,您会保留
很容易证明这给出了一个随机选择。看到 你为什么不能做这样的事情
我确定您的意思不是那么简单,您可以进一步指定吗? 好吧,您确实至少需要知道N在运行时是什么,即使这涉及对列表进行额外的传递以对其进行计数。最简单的算法是从N中选择一个随机数并删除该项目,重复k次。或者,如果允许返回重复数字,请不要删除该项目。
除非您有一个非常大的N,并且对性能的要求非常严格,否则此算法必须以 编辑:没关系,汤姆·霍顿的方法更好。首先选择随机数,然后遍历列表一次。我认为理论上的复杂度相同,但预期的运行时要好得多。 |