随机采样和随机选择

http://blog.csdn.net/pipisorry/article/details/44491727

离散分布的随机变量的取样问题

Question:随机播放音乐(随机数相关,带权重)

       假设张三的mp3里有1000首歌,现在希望设计一种随机算法来随机播放。与普通随机模式不同的是,张三希望每首歌被随机到的改了吧是与一首歌的豆瓣评分(0~10分)成正比的,如item0评分为8.9分,item1评分为9.5分,则希望听item0的概率与item1的概率比为89:95,。现在我们已知这1000首歌的豆瓣评分。

解决方案一

def randomSelect(item_list):
    '''
    随机选择带权重的list中的某个item,并返回其下标(item_list权重和可以不为1)
    :param item_list:
    :return:
    '''
    accu_item_list = add.accumulate(item_list)
    # print(type(accu_item_list))
    random_select = random.random() * accu_item_list[-1]
    for accu_item_id, accu_item in enumerate(accu_item_list):
        if accu_item > random_select:
            return accu_item_id


def cal_ratio(item_list):
    '''
    计算每个item在item_list中的比重
    :param item_list:
    :return:
    '''
    all_sum = sum(item_list)
    for i in item_list:
        print(i / all_sum)


if __name__ == '__main__':
    item_list = [0.1, 0.4, 0.6, 0.8, 0.3]
    cal_ratio(item_list)

    item_list_all = []
    item_list_cnt = []
    for i in range(100000):
        selected_item_id = randomSelect(item_list)
        item_list_all.append(selected_item_id)
    for i in range(len(item_list)):
        item_list_cnt.append(item_list_all.count(i))
    cal_ratio(item_list_cnt)

Note: 原理所有比重加和为accu_item_list[-1](可看成一维上的长度, 是所有item长度的和,且大比重的item长度相对更长), 在这个总长度上掷骰子,长度长的item选中概率大。

当然查找时可以使用折半查找。这样算法的时间复杂度即为折半查找的时间复杂度O(lgn),n是列表中歌曲的数目。

解决方案二

(1)1000首歌曲编号,从1至1000
(2)随机选择一首歌:产生一个1至1000的随机数,表示要播放的歌曲,这时,所有的歌曲被选中播放的概率是相同的
(3)选定的歌播放与否:假设选定的歌曲是54号,它的豆瓣评分是9.5分,那么此时再随机生成一个1至100的随机数,如果随机数小于等于95,那么就播放这首歌曲,如果随机数大于95,则重复1,2,3的步骤,直至找到一首可以播放的歌曲
备注:两首歌曲,评分分别为8.0,9.5,他们被选中的概率为1/1000,选中后还要产生一次随机数,被播放的概率分别为80%,95%,选中概率相同,播放概率比恰好是分数比值

详细解释:

重述算法本身:
1、以[1,N]均匀分布产生随机数s;
2、以[0,1]均与分布产生随机数q,若q<ps,则选择第s首歌,算法结束;否则,跳转到第1步。


下面的研究对象,都是仅考察第i首音乐:
假设它第n次被选中的概率为f(n),前n次被选中的概率为s(n),即s(n)=f(1)+f(2)+...+f(n)。
显然有:f(n) = s(n) - s(n-1)

第n+1次被选中的概率为:
f(n+1) = (1-s(n))(1/N) * pi其中,1-s(n)表示前n次都没有被选中。
从而:s(n)= 1 - (f(n+1)
N/pi)

令a = -N/pi,则:
s(n) = af(n+1) + 1
从而:s(n-1)=af(n) + 1
两式相减,得到:
f(n) = af(n+1) - af(n)
从而:q = f(n+1) / f(n) = (1+a)/a = (N-1)/N
而f(1)=pi/N
从而,s(n) = f(1) / (1 - q) = pi

结论仍然是:这种做法是对的。

此外,虽然啰嗦了这么多,再说两点:
1、通过上面的式子:f(n+1) / f(n) = (N-1)/N可以看出,其实第n+1次的概率比第n次的概率,是等比数列的。
2、以上仅仅是高中“等比数列”“通项公式和前n项和公式”的简单运算。

复杂度分析:

需要多少次才能成功选中一首歌的期望值

这个期望值E只和歌曲列表的平均分A有关,如果选了无数次还没有成功命中的话,只能说明是听歌的人品位太差。。。。。
夸张一点说,假如说某君从来没有成功定位过一首歌,说明他听的歌全都是0分哈哈哈哈哈哈哈哈
所以,从这个角度考虑,这个算法还是有一定缺点的
下面我来补充下我自己的想法吧,和其他已有的回答有相似之处,大家看看就好
假设列表里有1000首歌,每首歌的打分是0~100间的整数

  1. 定义一个大小1000的数组A[1000], 这个数组的每个元素分别存放第0~i首歌的打分之和,设数组最后一个元素为A[999]为M;
  2. 随机生成一个0~M间的随机数R;
  3. 利用折半查找,找到第一个大于等于R的元素的下标,则该下标即为选中的歌曲编号。

算法的时间复杂度即为折半查找的时间复杂度O(lgn),n是列表中歌曲的数目。

解决方案三

最快构建算法复杂度为O(n),采样时间复杂度O(1)。

[时间复杂度O(1)的离散采样算法—— Alias method/别名采样方法]

[采样方法总结:Alias method]

构建算法复杂度为O(n)的python实现[Alias Method for Sampling 采样方法]

解决方案四

直接调用numpy随机生成函数,如choice(['a', 'b', 'c'], p=[0.5, 0.3, 0.2])

[numpy教程:随机数模块numpy.random]

-柚子皮-

权重随机算法

      平时,经常会遇到权重随机算法,从不同权重的N个元素中随机选择一个,并使得总体选择结果是按照权重分布的。如广告投放、负载均衡等。
如有4个元素A、B、C、D,权重分别为1、2、3、4,随机结果中A:B:C:D的比例要为1:2:3:4。
**总体思路**:累加每个元素的权重A(1)-B(3)-C(6)-D(10),则4个元素的的权重管辖区间分别为[0,1)、[1,3)、[3,6)、[6,10)。然后随机出一个[0,10)之间的随机数。落在哪个区间,则该区间之后的元素即为按权重命中的元素。

**实现方法**:
利用TreeMap,则构造出的一个树为:
B(3)
/      \
​     /         \
​     A(1)     D(10)
​               /
​             /
​         C(6)
然后,利用treemap.tailMap().firstKey()即可找到目标元素。
当然,也可以利用**数组+二分查找**来实现。
**代码:**
```java
package com.xxx.utils;

import com.google.common.base.Preconditions;
import org.apache.commons.math3.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
 
public class WeightRandom<K,V extends Number> {
    private TreeMap<Double, K> weightMap = new TreeMap<Double, K>();
    private static final Logger logger = LoggerFactory.getLogger(WeightRandom.class);
 
    public WeightRandom(List<Pair<K, V>> list) {
        Preconditions.checkNotNull(list, "list can NOT be null!");
        for (Pair<K, V> pair : list) {
            double lastWeight = this.weightMap.size() == 0 ? 0 : this.weightMap.lastKey().doubleValue();//统一转为double
            this.weightMap.put(pair.getValue().doubleValue() + lastWeight, pair.getKey());//权重累加
        }
    }
 
    public K random() {
        double randomWeight = this.weightMap.lastKey() * Math.random();
        SortedMap<Double, K> tailMap = this.weightMap.tailMap(randomWeight, false);
        return this.weightMap.get(tailMap.firstKey());
    }
 
}
```

水库抽样-在未知个数的链表中均匀随机选1个元素

例如:list: node1 -> node2 -> node3 -> node4 -> node5 -> node6 -> NULL
这个链表有6元素,每个node可能被选到的概率为1/6。但一般情况下,node的个数是未知的(没有链表头没有记录个数的状态变量)。
写一个函数,如何在这种情况下,能均匀的概率选取出链表其中一个node?

解决方案:

遍历链表 对第i个节点 (i从1开始),均匀产生随机数 x
若x % i == 0 则暂时保留这个节点 (换掉之前保留的节点)
(1) 如果只有1个节点 选择第1个节点的概率是1
(2) 如果只有2个节点 第一步先选择第1个节点 然后再以1/2的概率换掉, 所以每个节点选择的概率是1/2
(3) 假设遍历到链表第n个节点的时候 选择目前保留的n个节点的概率都是1/n
则对第(n + 1)个节点, 我们有1/(n + 1)的概率选择它,有n / (n + 1)的概率保留原来保存的节点,所以保留前n个节点中每个的概率是(这是个条件概率) 1/n * (n / (n + 1)) = 1 / (n + 1) ,可见最终保留每个节点的概率都是1/(n + 1)
这种抽样方法叫水库抽样,可以扩展到要保留k个的情况。
from:http://blog.csdn.net/pipisorry/article/details/44491727

ref:http://ask.julyedu.com/question/127

http://ask.julyedu.com/question/315

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值