1. shuffle
对于当前位置i,等概率地从[0,i](或者[i,n-1])选取一个数。
1)从[0,i]选取
循环不变式:当前数组[0,i]的每个数在位置[0,i]出现的概率是相等的
随机函数形式:rand(i), 产生等概率的生成[0,i]上的数
2)从[i,j]选取
循环不变式:原数组的每个数在位置[0,i]出现的概率是相等的
随机函数形式:rand(i, j), 等概率的产生[i,j]之间的数。
2. 从一个文本文件中随机读取一行(流中随机取一个数)
不变式:对于已读的行,每一行都是等概率的被保存在selectedLine里的
import random
def get_random_line (filename):
index, selectedLine = 0, ''
with open(filename) as f:
for line in f.readlines():
if random.randint(0, index) == 0:
selectLine = line
index += 1
return selectLine
3 流随机抽样:一个流中随机选取k个数
先读k个数,之后每读一个数:
1)以k / n, (n为已读总数)的概率决定是否落到池子 if (randint(0, n - 1) < k)
2)随机取池中一个位置交换(其实是扔掉/覆盖,因为只关心数组的前k个数)
第二种更简单的理解及方法:
i为当前序号, 产生一个 [0, i]上的均匀的随机数 x,如果x < k,即落在[0, k-1]的池子里,则替换A[x]上的数。
和舍弃法的原理一样,如果随机变量等概率的落在一个区间[a, b],则一定等概率的落在其任意子区间。
vector<string> random_sample(ifstream &stream, int k) {
vector<string> pool(k);
string line;
for(int i = 1; !stream.eof(); ++i) {
stream >> line;
int index = rand() % i;
if (index < k) pool[index] = line;
}
return pool;
}
这是假设流的size远大于 k,否则有可能流读完了k个采样没取够,解决办法:重复这个过程,直到取够k个
vector<int> pick(vector<int> &A, int k) {
vector<int> pool(k), marked(k);
for (int count = 0; true; count = 0, marked = vector<int>(k)) {
for (int i = 0; i < A.size(); ++i) {
int index = rand() % (i + 1);
if (index < k) {
pool[index] = A[i];
if (marked[index] == 0) { //only count add, not replacment
marked[index] = 1;
++count;
}
}
}
if (count == k) return pool;
}
}
4. 给定函数rand7()可以随机产生[1, 7]之间的数,实现rand5() 可以随机产生[1,5]之间的数
舍弃法,调用rand7(),返回值大于5则舍弃,重新调用,直到返回值小于等于5
int rand7();
int rand5() {
while (ture) {
int r = rand7();
if(r<=5) return r;
}
}
5. 给定函数rand5(), 实现rand7()
思路:先用rand5() 级联出一个范围[1,n](n>=7)上的随机函数,然后再用舍弃法。
int rand5(){
return rand() % 5 + 1;
}
int rand7() {
while (true) {
int value = (rand5()-1) * 5 + rand5() -1;
if(value < 21 ) return value % 7 + 1;
}
}
6. 给你一堆数,每个数有个weight, 按weight 的概率sample一个数
1)想象一个数轴,每个数的weight占数轴上一段,等概率的sample数轴,落在哪一段就等于sample到对应段上的数。前缀和 + 二分搜索
int getByWeightedRandom(vector<int> &A, vector<int> W) {
for (int i = 1; i < W.size(); W[i] += W[i - 1], ++i);
int w = rand() % W.back();
int j = lower_bound(W.begin(), W.end(), w) - W.begin();
if (W[j] == w) ++j; // the interval is like [a, b)
return A[j];
}
2) 第二步,w落在哪个数的区间,也可以用反复sample法,随机选一个候选数,判断w是否落在这个数的weight区间,重复这个过程,直到满足条件。
int weighted_sample(vector<int> &A, vector<int> W) {
for (int i = 1; i < W.size(); ++i) W[i] += W[i - 1];
int w = rand() % W.back();
for (;;) {
int i = rand() % A.size();
if (w < W[i] && (i == 0 || w >= W[i - 1])) return A[i];
}
}
7. 用不均匀01随机数发生器构造均匀的01随机数发生器
int rand01NotEven();
int rand01Even() {
while (true) {
int x = rand01NotEven();
int y = rand01NotEven();
if (x != y) return x;
}
}