Shihanmax's blog
海量数据之随机选取
问题:
常规情况下,从一个长度为n的数组中等概率选取k个元素的方法:
1
2
3
4
5
selectedIdx = []
for i in k:
idx = random(1, n)
if not idx in selectIdx:
selectIdx.append(idx)
当数组长度未知时(如给定一个链表,长度未知),要求只扫描一遍链表的前提下,等概率选取k个数字,此时上述方法就不可用了。
先看一下基本的情况:
一、从长度未知的链表中等概率、随机选择1个数(Random Pick)
第一次直接选取头结点元素作为choice,而后以二分之一的概率决定是否用下一个替换它,以1/3的概率决定是否使用第三个元素替换choice…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int randomSelect(pHead) {
int select;
int count = 1;
ListNode pCurr = pHead;
while (pCurr != null) {
randomNum = random(1, count)
if (randomNum == 1) {
select = pCurr.val;
}
pCurr = pCurr.next;
count++;
}
return select;
}
二、从长度未知的链表中等概率、随机选择k个数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void randomSelect(pHead, k) {
ListNode pCurr = pHead;
int[] choice = new int[k];
i = k + 1;
while (pCurr != null) {
r = random(1, i);
if (1 <= r <= k) {
choice[r] = pCurr.val;
}
pCurr = pCurr.next;
i++;
}
}
证明如下:
对于第1个结点,被选中,且未在后续选取中被替换:
p = k/k+1 * k+1/k+2 * k+2/k+3 * … * n-1/n = k/n
对于第2个结点:
p = k/k+2 * k+2/k+3 * … * n-1/n = k/n
…