蓄水池算法

蓄水池算法

常用于:流数据的等概率采样(给定一个数据流,数据流长度N很大,且N直到处理完所有数据之前都不可知,请问如何在只遍历一遍数据(O(N))的情况下,能够随机选取出m个不重复的数据。转自:这里

m=1的情况下通俗理解:

(1)如果我们池子中只有一个数字,那么拿到第一个数字的概率就是100%毋庸置疑。
(2)两个数字50% 三个数字每个数字的几率都是33% 以此类推。。。。
当我们不知道池子里有多少个数字的时候,就需要用蓄水池的算法思想去计算。
(3)当链表前行到第一个数字,此时取第一个数字的几率为100%,那result自然等于这个数字。
(4)前进到第二个数字,那么此时取这个数字的几率自然就为50%(池子里只有两个数字),那么就是50%的几率取新数字,50%的几率保留原本的数字。
(5)第三个数字的时候,33%的几率取当前最新的这个数字,66%的几率保留原本的数字。这66%中:原本的数字有50%的几率是1,有50%的几率是2。也就是此时三个数字的概率都为33%。 通过这个算法,就能达到取数的概率均摊,从而实现随机。

附代码(@ADK):
class Solution {
    ListNode head;
    Random random;
    public Solution(ListNode head) {
        this.head=head;
        this.random=new Random();
    }
    
    public int getRandom() {
        // 蓄水池算法
        // 第一次拿第一个数 第一个数的概率是100%
        // 第二次 拿第二个数 取第二个数的概率是50%
        // 第三次 拿第三个数是 33% 则保留原本数的概率是66%
        ListNode p=this.head;
        int count=0;
        int res=0;
        while(p!=null){
            count++;
            int randomint=random.nextInt(count)+1;//因为生成的是[0,count)的值 而不包含count  所以要加1
            if(randomint==count){
                res=p.val;
            }
            p=p.next;
        }
        return res;
    }
}
m>1时:
先上代码
int[] reservoir = new int[m];

// init
for (int i = 0; i < reservoir.length; i++)
{
    reservoir[i] = dataStream[i];
}

for (int i = m; i < dataStream.length; i++)
{
    // 随机获得一个[0, i]内的随机整数
    int d = rand.nextInt(i + 1);
    // 如果随机整数落在[0, m-1]范围内,则替换蓄水池中的元素
    if (d < m)
    {
        reservoir[d] = dataStream[i];
    }
}

分析(作者@邱simple):
算法思路大致如下:
(1)如果接收的数据量小于m,则依次放入蓄水池。
(2)当接收到第i个数据时,i >= m,在[0, i]范围内取以随机数d,若d的落在[0, m-1]范围内,则用接收到的第i个数据替换蓄水池中的第d个数据。
(3)重复步骤2。
算法的精妙之处在于:当处理完所有的数据时,蓄水池中的每个数据都是以m/N的概率获得的。
通俗理解:
第i个接收到的数据最后能够留在蓄水池中的概率=第i个数据进入过蓄水池的概率*之后第i个数据不被替换的概率(第i+1到第N次处理数据都不会被替换)。

1.当i<=m时,数据直接放进蓄水池,所以第i个数据进入过蓄水池的概率=1。
2.当i>m时,在[1,i]内选取随机数d,如果d<=m,则使用第i个数据替换蓄水池中第d个数据,因此第i个数据进入过蓄水池的概率=m/i。
3.当i<=m时,程序从接收到第m+1个数据时开始执行替换操作,第m+1次处理会替换池中数据的为m/(m+1),会替换掉第i个数据的概率为1/m,则第m+1次处理替换掉第i个数据的概率为(m/(m+1))(1/m)=1/(m+1),不被替换的概率为1-1/(m+1)=m/(m+1)。依次,第m+2次处理不替换掉第i个数据概率为(m+1)/(m+2)...第N次处理不替换掉第i个数据的概率为(N-1)/N。所以,之后第i个数据不被替换的概率=m/(m+1)(m+1)/(m+2)...(N-1)/N=m/N。
4.当i>m时,程序从接收到第i+1个数据时开始有可能替换第i个数据。则参考上述第3点,之后第i个数据不被替换的概率=i/N。
5.结合第1点和第3点可知,当i<=m时,第i个接收到的数据最后留在蓄水池中的概率=1m/N=m/N。结合第2点和第4点可知,当i>m时,第i个接收到的数据留在蓄水池中的概率=m/ii/N=m/N。综上可知,每个数据最后被选中留在蓄水池中的概率为m/N。
这个算法建立在统计学基础上,很巧妙地获得了“m/N”这个概率。

两道相关的力扣题:382. 链表随机节点398. 随机数索引

posted @   迷路的风sdh  阅读(672)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示