经典算法:随机抽样

最近发现两个比较有意思的随机抽样算法,分享一下

1. 随机抽样且保持有序

需求:

一家公司购买了他们的第一批电脑,该公司的业务主要是民意调查,现在要开发一个程序:程序的输入是选区名列表以及整数 m,输出是随机选择的 m 个选区名列表。通常选区名有几百个,m 通常在 20 ~ 40。

程序描述:

程序的输入包含两个整数 m 和 n,其中 m<n。输出是0 ~ n-1 范围内的 m 个随机整数的有序列表,不允许重复。从概率的角度说,我们希望得到没有重复的有序选择,其中每个选择出现的概率相等。

简单点来说,就是有 n 个数, 随机取 m 个,并保持有序。

解法:

我们知道了 n 和 m,轮流判断 n 个数组成的列表中每个数的概率(m/n),每次判断后n=n-1,若当前被判断的数被选择,则m=m-1,否则 m 不变。

假设 5 个数选 2 个,那么意味着每个数的概率都是 2/5 。我们以 2/5 的概率去判断第 1 个数,那么结果有两种,选择1,不选择。当判断第 2 个的时候,在以选择了第 1 个数的情况下,选择 2 的概率是 (2-1)/(5-1)=1/4,在以没选择第 1 个数的情况下,选中 2 的概率是 2/(5-1)=2/4,所以第二个数的概率:(2/5)*(1/4) + (3/5)*(2/4) = 2/5。第二个数的概率和第一个数的概率相等。

证明:

证明公式

实现:

# Python
import random
# 抽样,从n个中抽m个
def sampling(lists, m, n=None):
    selected = []
    if n is None:
        n = len(lists)
    remaining = n-1
    for i in range(n):
        # random.random()返回 0 ~ 1的随机数
        if random.random() * remaining < m:
            selected.append(lists[i])
            m -= 1
        remaining -= 1
    return selected
# test
lists = [i for i in range(10)]
print sampling(lists, 3)
# 结果
>>>[4, 5, 7]

2. 在不知道总数的情况下随机选一个

如何从 n 个对象(可以依次看到这 n 个对象,但事先不知道 n 的值)中随机选取一个?例如,如何在事先不知道文本行数的情况下读取该文件,从中随机选择并输出一行?

解法:

我们先设一个变量叫selected,选择第 1 行赋值给 selected,并以 1/2 的概率选择第 2 行并重新赋值给selected, 以 1/3 的概率选择第 3 行并重新赋值给selected。在这以过程结束时, 每一行的选中概率都是相等的(都是 1/n, 其中 n 是文件的总行数)

要证明这个概率可以从最后一行算起,设最后一行的概率为P(n)=1/n, 倒数第二行的概率为P(n-1)=(1-P(n))*(1/(n-1)) = 1/n,倒数第m-1行概率为:

证明公式

代码:

# Python
import random

def getRandLine(text):
    i = 1
    selected = ''
    for line in text.splitlines():
        if (random.random() < (1.0/i)):
            selected = line
        i += 1
    return selected
# test
text = """\
line1
line2
line3
line4
line5
line6
line7
line8
line9
line10
""" 
print getRandLine(text)
# 结果
>>>line9
参考: 编程珠玑
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值