数学——概率统计--随机数生成¶
随机数生成器¶
C++生成随机数需要rand()
和srand()
。
两者的区别在于,rand()
函数每次的随机数种子是相同的,生成的是0-INT_MAX
之间的整数,是一种伪随机。srand()
一般的用法是srand(time(NULL))
,用系统时间初始化随机数种子,这样每次生成的序列是不同的。
#include <bits/stdc++.h>
using namespace std;
int main()
{
std::ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
for (int i = 0; i < 10; ++i) {
cout << (rand() % 100) << ' ';
}
cout << endl;
return 0;
}
上面没有初始化随机数种子,每次生成的序列都是
83 86 77 15 93 35 86 92 49 21
,如果使用srand()
:
#include <bits/stdc++.h>
using namespace std;
int main()
{
std::ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
srand(time(NULL));
for (int i = 0; i < 10; ++i) {
cout << (rand() % 100) << ' ';
}
cout << endl;
return 0;
}
85 58 84 46 3 34 71 17 18 49
69 23 38 58 67 85 81 74 63 90
95 0 45 28 80 24 82 64 52 28
会发现每次生成的序列都是不一样的。
常用规律总结:
- 随机生成
[0, n)
之间的整数:rand() % n
- 随即生成
[a, b)
之间的整数(a < b):rand() % (b - a) + a
- 随机生成
[a, b]
之间的整数(a \leq b):rand() % (b - a + 1) + a
- 随机生成
(a, b]
之间的整数(a < b):rand() % (b - a) + a + 1
- 取得
[0,1]
之间的浮点数:rand() * 1.0 / INT_MAX
非等概率随机数生成器转换成等概率随机数生成器¶
一类很经典的问题:一个随机数生成器以概率p
生成0,以概率1-p
生成1,如何让随机数生成器等概率的生成0和1?
两个相同的随机数生成器满足上述条件,记为f, g
,那么f
生成0并且g
生成1的概率是p(1-p)
,f
生成1并且g
生成0的概率是p(1-p)
。这样就可以等概率的生成0和1,用一个while
循环只有上述两种情况的时候输出。
不过存在的问题是假如p
很小,那么生成0的概率是很小的,也就意味着可能要循环多次才会出现想要的结果。
将问题一般化:如果以概率p_1生成0,概率p_2生成1,\cdots,以概率p_n生成n-1
,如何等概率的生成0-n-1?其中\sum_{i=1}^n p_i = 1。
等概率随机数生成器转成非等概率随机数生成器¶
如果一个随机数生成器等概率的生成0和1,如何让随机数生成器以概率p
生成0,以概率1-p
生成1,相当于上面问题的逆问题。
可以假定概率p
以浮点数表示,精度为2位,比如p = 0.23
,那么1 - p = 0.77
,可以用随机数生成器等概率的生成0-99共100个数,其中0-22就输出0,23-99就输出1。如果精度是3或4等,就乘以相应的10^n。
#include <bits/stdc++.h>
using namespace std;
int randomPick(int p)
{
return rand() % 100 < p ? 0 : 1;
}
int main()
{
std::ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
double p; cin >> p;
int prob = p * 100;
cout << randomPick(prob) << endl;
return 0;
}
问题拓展,如何以概率p_1, p_2, \cdots ,p_n生成0到n-1
,其中\sum_{i=1}^n p_i = 1。
根据权重生成随机数¶
- LeetCode 528.Random Pick with Weight
给定一个正整数数组 w
,其中 w[i]
代表位置 i
的权重,请写一个函数 pickIndex
,它可以随机地获取位置 i
,选取位置 i
的概率与 w[i]
成正比。
题意是给定一个权重数组,根据权重占整体的比例(记为p
),通过函数pickIndex
以概率p
返回其对应的下标。描述很拗口,就以第二个样例作为例子:
给定权重数组1,3
,其中1
占整体的比例为1/4
,3占整体的比例为3/4
,那么函数pickIndex
应该以1/4
的概率返回0(对应1的下标),以3/4
的概率返回下标1
。
不妨设权重总和为total
,那么我们生成0-total - 1
之间的整数,实际上就可以根据权重来对数字进行划分:
0 | 1 2 3
随机数生成器等概率的生成0-3之间的数字,那么假如生成的数字为0,就返回下标0,生成1或2或3,就返回下标1,这样就满足根据权重返回下标了。
为了确定数字和下标对应的关系,我们需要计算前缀和:
前缀和下标: 0 1 2
前缀和数据: 0 1 4
实际上确定对应关系就是根据前缀和数据查找第一个大于数字的值,用其在前缀和的下标然后减一。比如生成数字2,查找到的位置是4对应的下标2,pickIndex
就返回2-1
,查找过程很容易就联想到利用二分法。
class Solution {
vector<int> preSum;
int n;
public:
Solution(vector<int>& w) {
std::ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
n = w.size();
preSum.resize(n + 1, 0);
for (int i = 1; i <= n; ++i) {
preSum[i] = preSum[i - 1] + w[i - 1];
}
}
int pickIndex() {
//srand(time(NULL));
int total = preSum[n];
int target = rand() % total;
int res = upper_bound(preSum.begin() + 1, preSum.end(), target) - preSum.begin();
return res - 1;
}
};
/**
* Your Solution object will be instantiated and called as such:
* Solution* obj = new Solution(w);
* int param_1 = obj->pickIndex();
*/
随机数索引¶
- LeetCode 398.Random Pick Index
给定一个可能含有重复元素的整数数组,要求随机输出给定的数字的索引。 您可以假设给定的数字一定存在于数组中。
注意: 数组大小可能非常大。 使用太多额外空间的解决方案将不会通过测试。
利用一个unordered_map<int, vector<int>> um
存储每个数值对应的下标,那么随机返回一个索引,其实就是生成一个0-um[target].size() - 1
的一个随机数,这样如果存在多次pick
,那么每次都是O(1)的效率。
class Solution {
vector<int> preSum;
int n;
public:
Solution(vector<int>& w) {
std::ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
n = w.size();
preSum.resize(n + 1, 0);
for (int i = 1; i <= n; ++i) {
preSum[i] = preSum[i - 1] + w[i - 1];
}
}
int pickIndex() {
//srand(time(NULL));
int total = preSum[n];
int target = rand() % total;
int res = upper_bound(preSum.begin() + 1, preSum.end(), target) - preSum.begin();
return res - 1;
}
};
/**
* Your Solution object will be instantiated and called as such:
* Solution* obj = new Solution(w);
* int param_1 = obj->pickIndex();
*/