取样问题、洗牌算法

《Programming Pearls 2th edition》第12章当中的"取样问题"非常有趣,现在摘录一些下来


洗牌算法,随机打乱一个0、1、2、...、n-1序列当中的数字

import random
def shuffle_1(x):
	for i in range(len(x)-1):
		j = random.randint(i, len(x)-1)
		x[i], x[j] = x[j], x[i]

这个算法可以理解为:n个人以1/n的概率竞选第1个位置,剩下的n-1个人以1/(n-1)概率获得第2个位置,……最后2个人以1/2机会获得第n-1位置。

保证了生成大小为n的全排列中每一个排列的机会是相等的。


取样问题:

p1  从n个数当中随机取m个数,每个数被选中的概率均等

特殊化,考虑m为1的情况


p2  从n个数当中随机选取m个数,每个大小为m的子集选中的概率均等


p3  从n个数当中随机选取m个数,每个大小为m的排列选中的概率均等


p4 从一列数字当中选取m个数,但是不知道数列的大小,每个数被选中的概率均等

特殊化 ,考虑 m为1的情况




解答:

p1

def sample_1(x, m):
	n = len(x)
	if m >= n:
		return list(x)
	ret = []
	u = m
	w = n
	for i in range(n):
		if w == 0:
			break
		if random.randint(1, w) <= u:
			u -= 1			
			ret.append(i)
		w -= 1	
	return ret		
u代表需要选取的数量,w代表列表中的备选数量。对于剩下来没有选中的数,以u/w的概率依次选取。因此每个数被选取的概率在剩下的数当中都是平等的。例如u=2, w=4,接下来的数有2/4的概率被选取。一旦被选取,接下来的一个数有1/3的概率获选;否则,有2/3概率。每一个局部的公平保持了整体的公平。

p2等价于p1


p3 

def sample_3(x, m):
	for i in range(m):
		j = random.randint(i, len(x)-1)
		x[i], x[j] = x[j], x[i]	
	ret = x[:m]
	return ret

这个算法可以理解为:n个人中找一个人以1/n机会获得第1个位置;剩下的n-1个人再找一个人以1/(n-1)的机会获取第2个位置;依此类推,直到前m个位置都有人选。此算法和洗牌算法的原理一致,只是洗牌算法中m等于n。

这个算法保证了从n个数中随机选取m个人,且m个人的排列是随机的


p4

def sample_4(x, m):
        ret = []
        for i in range(len(x)):
                if len(ret) < m:
                        ret.append(x[i])
                elif len(ret) == m:
                        if random.randint(0, i) <= m:
                                j = random.randint(0, m-1)
                                ret[j] = x[i]
        return ret

x中前m个数先放到位置上。接下来的第i个数,以m/i的概率被选中。从已选的m个数当中选择一个数换出来。

这个算法保证了每个数都有相同的概率被选中。

从第m+1个数开始,选中后所处的位置也是随机的。但是前m个数如果中途没有被换出来,它们的位置不是随机的。

因此,此算法选出的数的排列不是随机的。需要开始对前m个数随机排序,才能得到随机的排序。

进一步阅读

《the Art of Computer Programming》第2卷第3章“随机算法”


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值