水塘抽样算法

1. 介绍

对一个数量未知的样本,希望只经过一次遍历就完成随机抽样,即时间复杂度O(n)
因为样本数量未知,因此就不能通过random函数直接随机抽样
策略为从前往后遍历,每个样本成为答案的概率为1/i,其中i为样本编号,最终可以使每个样本概率为1/n
容易证明该做法的正确性,假设最终成为答案的样本编号为k,那么成为答案的充要条件为遍历大于k的所有元素时均没有被选择(没有覆盖)

\[P=\frac{1}{k}*\frac{k}{k+1}*\frac{k+1}{k+2}*...*\frac{n-1}{n} \]

简化后可得

\[P=\frac{1}{n} \]

2. 链表随机节点

给你一个单链表,随机选择链表的一个节点,并返回相应的节点值。每个节点 被选中的概率一样

int getRandom() {
        ListNode* phead = this->head;
        int val ;
        int count = 1;//从第一个数开始
        while (phead){
            if (rand() % count++ == 0)//转移概率
                val = phead->val;//发生转移
            phead = phead->next;//下一个节点
        }
        return val;
    }

3. 非重叠矩形的随机点

这里使用水塘抽样法根据权重(点数)遍历一遍即可

class Solution {
private:
    vector<vector<int>> recs;
public:
    Solution(vector<vector<int>>& rects):recs(rects) {}
    
    vector<int> pick() {
        int idx = -1, cur = 0, curSum = 0, n = recs.size();
        for(int i = 0; i < n; ++i)
        {
            int x1 = recs[i][0], y1 = recs[i][1], x2 = recs[i][2], y2 = recs[i][3];
            cur = (x2-x1+1) * (y2-y1+1);//计算当前矩形点数
            curSum += cur;//计算总点数
            if(rand()%curSum < cur)//当前权重概率
                idx = i;//发生转移
        }
        int x1 = recs[idx][0], y1 = recs[idx][1], x2 = recs[idx][2], y2 = recs[idx][3];
        return {x1 + rand()%(x2-x1+1), y1 + rand()%(y2-y1+1)};//对选定矩形选取随机位置
    }
};
posted @ 2022-06-09 22:23  失控D大白兔  阅读(50)  评论(0编辑  收藏  举报