• 几个随机算法【转载+整理】


    淘宝搜索技术博客 http://www.searchtb.com/2010/12/random-algorithm.html

     

    本文内容

    • 随机选取数据
    • 随机数的计算
    • 总结

    细看这篇文章,有点惊讶,没想到一个随机数,有这么多需要思考的东西。

    在日常工作中,经常需要使用随机算法。比如面对大量的数据, 需要从其中随机选取一些数据来做分析。 又如在得到某个分数后, 为了增加随机性, 需要在该分数的基础上, 添加一个扰动, 并使该扰动服从特定的概率分布。本文主要从这两个方面出发, 介绍一些算法, 供大家参考。

    假设,我们有一个使用的随机函数 float frand(), 返回值在(0, 1)上均匀分布。大多数的程序语言库提供这样的函数。 在其他的语言如 C/C++ 中, 可以通过间接方法得到。如:

    clip_image002

     

    随机选取数据


    假设有一个集合 clip_image002[4],如何从集合A中等概率地选取 m 个元素,0≤m≤n

    通过计算古典概率公式可以得到,每个元素被选取的概率为 m/n。如果集合 A 里面的元素本来就具有随机性,每个元素在各个位置上出现的概率相等,并且只在 A 上选取一次数据,那么直接返回 A 的前面 m 个元素就可以,或每隔 k 个元素取一个等类似的方法。但这样的算法局限很大,对集合 A 的要求很高,因此下面介绍两种其他的算法。

    假设集合A中的元素在各个位置上不具有随机性,并已经按某种方式排序,那么我们可以遍历集合A中的每一个元素 clip_image004,根据一定的概率选取 clip_image004[1]

    clip_image006 为还需要从A中选取的元素个数,clip_image008 为元素 clip_image004[2] 及其右边的元素个数,即 clip_image010,那么选取元素 clip_image004[3] 的概率为 clip_image012。该证明略过。

    简单计算前面两个元素 clip_image014 各自被选中的概率。设 clip_image016 表示 clip_image004[4] 被选中的概率,相应 clip_image018 为未被选中。

    1)第一个元素被选中概率 clip_image020,而 clip_image022

    2)第二个元素被选中的概率

    clip_image024

    用 C++ 实现了上述算法,如下:

    template<class T>
    bool getRand(const vector<T> vecData, int m, vector<T>& vecRand)
    {
        int32_t nSize = vecData.size();
        if(nSize < m || m < 0)
            return false;
        vecRand.clear();
        vecRand.reserve(m);
        for(int32_t i = 0, isize = nSize; i < isize ; i++){
                float fRand = frand();
                if(fRand <=(float)(m)/nSize){
                    vecRand.push_back(vecData[i]);
                    m--;
                }
            nSize --;
        }
        return true;
    }

    上述算法,在 m=4,n=10,选取 100 万次的情况下,统计每个位置元素被选取的概率:

    位置

    概率

    1

    0.399912

    2

    0.400493

    3

    0.401032

    4

    0.399447

    5

    0.399596

    6

    0.39975

    7

    0.4

    8

    0.399221

    9

    0.400353

    10

    0.400196

    此外,还有很多其他算法,不再单独介绍。如对第 i 个数,随机从 clip_image002[6] 中取一个数与 clip_image004[12] 交换。

    在有些情况下,我们不能直接从 A 得到元素个数,如从一个很大的数据文件中随机选取几条数据出来。在内存不充足的情况下,为了知道我们文件中数据的个数,我们需要先遍历整个文件,然后再遍历一次文件利用上述的算法随机的选取m个元素。或是在类似 hadoop 的 reduce 方法中,只能得到数据的迭代器。我们不能多次遍历集合,只能将元素存放在内存中。在这些情况下,如果数据文件很大,那么算法的速度会受到很大的影响,而且对 reduce 机器的配置也有依赖。

    此时,可以尝试一种只遍历一次集合的算法。

    1)取前m个元素放在集合 A’中;

    2)对于第 i 个元素(i>m),使 i m/i 的概率下,等概率随机替换 A’中的任意一个元素。直到遍历完集合;

    3)返回 A’

    下面证明在该算法,每一个元素被选择的概率为 m/n

    1)       当遍历到到 m+1 个元素时, 该元素被保存在 A’中的概率为 m/(m+1),前面m个元素被保存在A’中的概率为 1- (m/m+1 * 1/m) = m/m+1

    2)       当遍历到第 i 个元素时,设前面 i-1 个元素被保存在 A’中的概率为 m/(i-1)。根据算法, 第i个元素被保存在 A’中的概率为 m/i,前面 i-1 各个元素留在 A’中的概率为 m/(i-1) * (1-(m/i* 1/m) = m/i

    3)       通过归纳,即可得到每个元素留在 A’中的概率为 m/n

    在类似 hadoop 的 reduce 函数中,用 Java 实现该算法。

    public void reduce(TextPair key, Iterator value, OutputCollector collector, int m)
    {
        Text[] vecData = new Text[m];
        int nCurrentIndex = 0;
        while(value.hasNext()){
            Text tValue = value.next();
            if(nCurrentIndex < m){
               vecData[nCurrentIndex] = tValue;
            }
            else if(frand() < (float)m / (nCurrentIndex+1)) {
               int nReplaceIndex = (int)(frand() * m);
               vecData[nReplaceIndex] = tValue;
            }
            nCurrentIndex ++;
        }
     //collect data
    …….
    }

    利用上述算法,在 m=4,n=10,经过 100 万次选取之后, 计算每个位置被选择的概率:

    位置

    概率

    1

    0.400387

    2

    0.400161

    3

    0.399605

    4

    0.399716

    5

    0.400012

    6

    0.39985

    7

    0.399821

    8

    0.400871

    9

    0.400169

    10

    0.399408

     

    随机数的计算


    在搜索排序中,有些时候我们需要给每个搜索文档的得分添加一个随机扰动, 并且让该扰动符合某种概率分布。

    假设,我们有一个概率密度函数 clip_image002[8],并且有 clip_image004[14]。那么可以利用 f(x) frand 设计一个随机计算器 clip_image006[4],使得 clip_image006[5] 返回的数据分布,符合概率密度函数 f(x)。令

    clip_image008[4]

    那么函数

    clip_image010[4]

    符合密度函数为 f(x) 的分布。

    下面对以上公式进行简单的证明:

    由于g(x) 是单调函数, 并且 x [0,1] 上均匀分布,那么

    clip_image012[4]

    由于上述公式太复杂,计算运算量大,在线上实时计算的时候通常采用线性差值的方法。算法为:

    1)在 offline 计算的时候,设有数组 double A[N+1];对于所有的 i, 0<=i<=N, 令

    clip_image002[10]


    2)在线上实时计算的时候,令

    clip_image015

    则线性插值的结果为:

    clip_image017

    我们做了一组实验,令 f(x) 服从标准正太分布 N(0,1), N=10000, 并利用该算法取得了 200*N 个数。对这些数做了个简单的统计, 得到 x 轴上每个小区间的概率分布图。

    clip_image018


     

    总结


    在日常工作中, 还有其他一些有趣的算法。比如对于 top 100w 的 query, 每个 query 出现的频率不一样, 需要从这 100w 个 query, 按照频率越高, 概率越高的方式随机选择query。限于篇幅, 就不一一介绍了。

  • 相关阅读:
    Scrum与看板区别
    Android中的Apk的加固(加壳)原理解析和实现
    规模化敏捷开发的10个最佳实践
    TDD、BDD、ATDD、DDD 软件开发模式
    如何解决秒杀的性能问题和超卖的讨论
    mongo数据库的各种查询语句示例
    linux if -d -e -f表达的意思
    prometeus, grafana部署以及监控mysql
    2019年目标
    nginx 动态添加ssl模块
  • 原文地址:https://www.cnblogs.com/liuning8023/p/3202089.html
Copyright © 2020-2023  润新知