水塘抽样算法
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)};//对选定矩形选取随机位置
}
};