【发布时间】:2011-04-29 07:17:59
【问题描述】:
我有一个单向链表,但不知道它的大小。
我想在这个列表中得到一个随机元素,我只有一次机会遍历这个列表。 (我不允许遍历两次或多次)
这个问题的算法是什么?谢谢!
【问题讨论】:
标签: algorithm random traversal
我有一个单向链表,但不知道它的大小。
我想在这个列表中得到一个随机元素,我只有一次机会遍历这个列表。 (我不允许遍历两次或多次)
这个问题的算法是什么?谢谢!
【问题讨论】:
标签: algorithm random traversal
这只是 reservoir sampling,水库大小为 1。
其实很简单
这是均匀抽样的,因为在一天结束时选择任何元素的概率是 1/n(读者练习)。
【讨论】:
这可能是一个面试问题。数据科学家使用水库抽样从大量数据流中将相关数据存储在有限的存储空间中。
如果你必须从任何元素 n 的数组中收集 k 个元素,这样你收集到的每个元素的概率应该是相同的 (k/n),你遵循两个步骤,
1) 在存储中存储前 k 个元素。 2)当下一个元素(k + 1)来自流时,显然你的集合中没有空间了。生成一个从o到n的随机数,如果生成的随机数小于k,假设l,替换storage[l ] 与流中的 (k+1) 个元素。
现在,回到你的问题,这里的存储大小是 1。所以你将选择第一个节点,迭代列表中的第二个元素。现在生成随机数,如果它是 1,则不理会样本,否则切换列表中的存储元素
【讨论】:
这个问题可以使用水库采样来完成。它基于从 n 个项目中选择 k 个随机项目,但这里的 n 可能非常大(不一定要放入内存!)并且(如您的情况)最初是未知的。
维基百科有一个可以理解的算法,我在下面引用:
array R[k]; // result
integer i, j;
// fill the reservoir array
for each i in 1 to k do
R[i] := S[i]
done;
// replace elements with gradually decreasing probability
for each i in k+1 to length(S) do
j := random(1, i); // important: inclusive range
if j <= k then
R[j] := S[i]
fi
done
这个问题只需要 1 个值,所以我们取 k=1。
C 实现:
【讨论】:
这是我找到的最简单的方法,效果很好并且可以理解:
public int findrandom(Node start) {
Node curr = start;
int count = 1, result = 0, probability;
Random rand = new Random();
while (curr != null) {
probability = rand.nextInt(count) + 1;
if (count == probability)
result = curr.data;
count++;
curr = curr.next;
}
return result;
}
【讨论】: