在用C++实现SCE-UA算法的CCE部分时,遇到这样一个具体问题:
对一个具有离散分布的总体进行不放回抽样,比如有一个总体个数为24的数组{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24}
,各位的概率分别为{48, 46, 44, 42, 40, 38, 36, 34, 32, 30, 28, 26, 24, 22, 20, 18, 16, 14, 12, 10, 8, 6, 4, 2}
,换算成小数为{0.08,0.076667,0.073333,0.07,0.066667,0.063333,0.06,0.056667,0.053333,0.05,0.046667,0.043333,0.04,0.036667,0.033333,0.03,0.026667,0.023333,0.02,0.016667,0.013333,0.01,0.006667,0.003333}
,权重总和为1,从中不放回地抽取10个数,要求每个数字都按照离散分布所具有的概率抽取。
在C++实现过程中,直接对数组进行抽样可能比较困难,因此可以对这个数组的下标0-23
共24个连续整数进行抽样,每个下标所具有的概率与实际对应的数的概率相同。路径E:\Master\study\Cpp\RandomSample
包含头文件 #include
,用到其中的std::sample
从总体中抽样。但是只能均匀抽样,而不能按照指定的权重抽样,即无法把离散分布dist
放入到sample
中作为参数,因此采用这种方法是行不通的。
测试代码:
#include
#include
#include
#include
#include
#include
int main()
{
//随机数引擎采用设备熵值保证随机性
auto gen = std::mt19937{ std::random_device{}() };
std::vector<int> wts(24); //存储权重值
std::vector<int> in(24); //存储总体
std::vector<int> out; //存储抽样结果
std::map<int, int> count; //输出计数
int sampleSize = 10; //抽取样本的数量
int sampleTimes = 10000; //抽取样本的次数
//权重赋值
for (int i = 0; i < 24; i++)
{
wts.at(i) = 48 - 2 * i;
}
//总体赋值
for (int i = 0; i < 24; i++)
{
in.at(i) = i + 1;
}
//产生按照给定权重的离散分布
std::discrete_distribution<size_t> dist{ std::begin(wts), std::end(wts) };
auto probs = dist.probabilities(); // 返回概率计算结果
//输出概率计算结果
std::copy(probs.begin(), probs.end(), std::ostream_iterator<double>
{ std::cout << std::fixed << std::setprecision(5), " "});
std::cout << std::endl;
//抽样测试
for (size_t j = 0; j < sampleTimes; j++)
{
//测试sample函数
std::sample(in.begin(), in.end(), std::back_inserter(out), sampleSize, gen);
for (size_t i = 0; i < sampleSize; i++)
{
//std::cout << out.at(i) << " "; //输出抽样结果
count[out.at(i)] += 1; //抽样结果计数
}
out.clear(); //清空输出数组,为下次抽样做准备
}
double sum = 0.0; //用于概率求和
//输出抽样结果
for (size_t i = 1; i <= 24; i++)
{
std::cout << i << "共有" << count[i] << "个 频率为:" << count[i] / double(sampleTimes * sampleSize) << std::endl;
sum += count[i] / double(sampleTimes * sampleSize);
}
std::cout << "总频率为:" << sum << std::endl; //输出总概率
std::cin.get(); //保留控制台窗口
return 0;
}
输出结果:
0.08000 0.07667 0.07333 0.07000 0.06667 0.06333 0.06000 0.05667 0.05333 0.05000 0.04667 0.04333 0.04000 0.03667 0.03333 0.03000 0.02667 0.02333 0.02000 0.01667 0.01333 0.01000 0.00667 0.00333
1共有4147个 频率为:0.04147
2共有4169个 频率为:0.04169
3共有4147个 频率为:0.04147
4共有4157个 频率为:0.04157
5共有4152个 频率为:0.04152
6共有4161个 频率为:0.04161
7共有4158个 频率为:0.04158
8共有4184个 频率为:0.04184
9共有4046个 频率为:0.04046
10共有4087个 频率为:0.04087
11共有4174个 频率为:0.04174
12共有4074个 频率为:0.04074
13共有4197个 频率为:0.04197
14共有4196个 频率为:0.04196
15共有4221个 频率为:0.04221
16共有4188个 频率为:0.04188
17共有4234个 频率为:0.04234
18共有4250个 频率为:0.04250
19共有4185个 频率为:0.04185
20共有4207个 频率为:0.04207
21共有4210个 频率为:0.04210
22共有4195个 频率为:0.04195
23共有4131个 频率为:0.04131
24共有4130个 频率为:0.04130
总频率为:1.00000
需要包含头文件#include
,用到其中的std::discrete_distribution
产生离散分布上的随机整数。每次仅从离散分布中抽取一个数字,放入集合set
中,利用集合无重复和有序的特点,循环抽取直至样本数达到要求。
测试代码:
#include
#include
#include
#include
#include
#include
int main()
{
//随机数引擎采用默认引擎
std::default_random_engine rng;
//随机数引擎采用设备熵值保证随机性
auto gen = std::mt19937{ std::random_device{}() };
std::vector<int> wts(24); //存储权重值
std::vector<int> in(24); //存储总体
std::set<int> out; //存储抽样结果
std::map<int, int> count; //输出计数
int sampleCount = 0; //抽样次数计数
int index = 0; //抽取的下标
int sampleSize = 24; //抽取样本的数量
int sampleTimes = 100000; //抽取样本的次数
//权重赋值
for (int i = 0; i < 24; i++)
{
wts.at(i) = 48 - 2 * i;
}
//总体赋值并输出
std::cout << "总体为24个:" << std::endl;
//赋值
for (int i = 0; i < 24; i++)
{
in.at(i) = i + 1;
std::cout << in.at(i) << " ";
}
std::cout << std::endl;
//产生按照给定权重的离散分布
std::discrete_distribution<size_t> dist{ std::begin(wts), std::end(wts) };
auto probs = dist.probabilities(); // 返回概率计算结果
//输出概率计算结果
std::cout << "总体中各数据的权重为:" << std::endl;
std::copy(probs.begin(), probs.end(), std::ostream_iterator<double>
{ std::cout << std::fixed << std::setprecision(5), " "});
std::cout << std::endl << std::endl;
//==========抽样测试==========
for (size_t j = 0; j < sampleTimes; j++)
{
index = dist(gen);
//std::cout << index << " "; //输出抽样结果
count[index] += 1; //抽样结果计数
}
double sum = 0.0; //用于概率求和
//输出抽样结果
std::cout << "总共抽样" << sampleTimes << "次," << "各下标的频数及频率为:" << std::endl;
for (size_t i = 0; i < 24; i++)
{
std::cout << i << "共有" << count[i] << "个 频率为:" << count[i] / double(sampleTimes) << std::endl;
sum += count[i] / double(sampleTimes);
}
std::cout << "总频率为:" << sum << std::endl << std::endl; //输出总概率
//==========抽样测试==========
//从总体中抽样放入集合中,直至集合大小达到样本数
while (out.size() < sampleSize)
{
index = dist(gen);
out.insert(index);
sampleCount += 1;
}
//输出抽样结果
std::cout << "从总体中抽取的" << sampleSize <<"个样本的下标索引为:" << std::endl;
for (auto iter : out)
{
std::cout << iter << " ";
}
std::cout << std::endl;
//输出抽样次数
std::cout << "抽样次数为:" << sampleCount << std::endl;
out.clear(); //清空输出集合,为下次抽样做准备
std::cin.get(); //保留控制台窗口
return 0;
}
输出结果:
总体为24个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
总体中各数据的权重为:
0.08000 0.07667 0.07333 0.07000 0.06667 0.06333 0.06000 0.05667 0.05333 0.05000 0.04667 0.04333 0.04000 0.03667 0.03333 0.03000 0.02667 0.02333 0.02000 0.01667 0.01333 0.01000 0.00667 0.00333
总共抽样100000次,各下标的频数及频率为:
0共有7935个 频率为:0.07935
1共有7674个 频率为:0.07674
2共有7355个 频率为:0.07355
3共有6931个 频率为:0.06931
4共有6596个 频率为:0.06596
5共有6386个 频率为:0.06386
6共有6002个 频率为:0.06002
7共有5691个 频率为:0.05691
8共有5375个 频率为:0.05375
9共有4963个 频率为:0.04963
10共有4747个 频率为:0.04747
11共有4314个 频率为:0.04314
12共有3908个 频率为:0.03908
13共有3770个 频率为:0.03770
14共有3370个 频率为:0.03370
15共有3026个 频率为:0.03026
16共有2706个 频率为:0.02706
17共有2319个 频率为:0.02319
18共有1989个 频率为:0.01989
19共有1680个 频率为:0.01680
20共有1285个 频率为:0.01285
21共有981个 频率为:0.00981
22共有668个 频率为:0.00668
23共有329个 频率为:0.00329
总频率为:1.00000
从总体中抽取的24个样本的下标索引为:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
抽样次数为:264
可以看到,为了抽取24个样本,竟然对总体抽样了264次才完成抽取,这效率无疑是极低的。
在完成每次抽取后,将抽到的下标索引的权重设置为0,保证不会再被抽取到,这样就保证抽取次数就和样本数相同了。要注意把最后一次抽取单独出来,是避免样本数等于总体的情况下,最后一次抽取后将所有权重都为0的权重数组赋值给离散分布dist,产生错误。考虑到样本等于总体的情况较少,因此没有在之前加一个样本是否等于总体的判断,而是把最后一次抽取单独在循环外执行,提高执行效率,避免判断带来的开销。
测试代码:
//STL改进方案
#include
#include
#include
#include
#include
#include
int main()
{
//随机数引擎采用默认引擎
std::default_random_engine rng;
//随机数引擎采用设备熵值保证随机性
auto gen = std::mt19937{ std::random_device{}() };
std::vector<int> wts(24); //存储权重值
std::vector<int> in(24); //存储总体
std::set<int> out; //存储抽样结果
std::map<int, int> count; //输出计数
int sampleCount = 0; //抽样次数计数
int index = 0; //抽取的下标
int sampleSize = 24; //抽取样本的数量
int sampleTimes = 100000; //抽取样本的次数
//权重赋值
for (int i = 0; i < 24; i++)
{
wts.at(i) = 48 - 2 * i;
}
//总体赋值并输出
std::cout << "总体为24个:" << std::endl;
//赋值
for (int i = 0; i < 24; i++)
{
in.at(i) = i + 1;
std::cout << in.at(i) << " ";
}
std::cout << std::endl;
//产生按照给定权重的离散分布
std::discrete_distribution<size_t> dist{ wts.begin(), wts.end() };
auto probs = dist.probabilities(); // 返回概率计算结果
//输出概率计算结果
std::cout << "总体中各数据的权重为:" << std::endl;
std::copy(probs.begin(), probs.end(), std::ostream_iterator<double>
{ std::cout << std::fixed << std::setprecision(5), " "});
std::cout << std::endl << std::endl;
//==========抽样测试==========
for (size_t j = 0; j < sampleTimes; j++)
{
index = dist(gen);
//std::cout << index << " "; //输出抽样结果
count[index] += 1; //抽样结果计数
}
double sum = 0.0; //用于概率求和
//输出抽样结果
std::cout << "总共抽样" << sampleTimes << "次," << "各下标的频数及频率为:" << std::endl;
for (size_t i = 0; i < 24; i++)
{
std::cout << i << "共有" << count[i] << "个 频率为:" << count[i] / double(sampleTimes) << std::endl;
sum += count[i] / double(sampleTimes);
}
std::cout << "总频率为:" << sum << std::endl << std::endl; //输出总概率
//==========抽样测试==========
//从总体中抽样放入集合中,直至集合大小达到样本数
while (out.size() < sampleSize - 1)
{
index = dist(gen); //抽取下标
out.insert(index); //插入集合
sampleCount += 1; //抽样次数增加1
wts.at(index) = 0; //将抽取到的下标索引的权重设置为0
dist.param({ wts.begin(), wts.end() });
probs = dist.probabilities(); // 返回概率计算结果
//输出概率计算结果
std::cout << "总体中各数据的权重为:" << std::endl;
std::copy(probs.begin(), probs.end(), std::ostream_iterator<double>
{ std::cout << std::fixed << std::setprecision(5), " "});
std::cout << std::endl << std::endl;
}
//最后一次抽取,单独出来是避免将所有权重都为0的权重数组赋值给离散分布dist,避免报错
index = dist(gen); //抽取下标
out.insert(index); //插入集合
sampleCount += 1; //抽样次数增加1
//输出抽样结果
std::cout << "从总体中抽取的" << sampleSize << "个样本的下标索引为:" << std::endl;
for (auto iter : out)
{
std::cout << iter << " ";
}
std::cout << std::endl;
//输出抽样次数
std::cout << "抽样次数为:" << sampleCount << std::endl;
out.clear(); //清空输出集合,为下次抽样做准备
std::cin.get(); //保留控制台窗口
return 0;
}
运行结果:
总体为24个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
总体中各数据的权重为:
0.08000 0.07667 0.07333 0.07000 0.06667 0.06333 0.06000 0.05667 0.05333 0.05000 0.04667 0.04333 0.04000 0.03667 0.03333 0.03000 0.02667 0.02333 0.02000 0.01667 0.01333 0.01000 0.00667 0.00333
总共抽样100000次,各下标的频数及频率为:
0共有8069个 频率为:0.08069
1共有7804个 频率为:0.07804
2共有7379个 频率为:0.07379
3共有6965个 频率为:0.06965
4共有6705个 频率为:0.06705
5共有6389个 频率为:0.06389
6共有5852个 频率为:0.05852
7共有5679个 频率为:0.05679
8共有5394个 频率为:0.05394
9共有5040个 频率为:0.05040
10共有4600个 频率为:0.04600
11共有4247个 频率为:0.04247
12共有3937个 频率为:0.03937
13共有3618个 频率为:0.03618
14共有3350个 频率为:0.03350
15共有3006个 频率为:0.03006
16共有2706个 频率为:0.02706
17共有2308个 频率为:0.02308
18共有2034个 频率为:0.02034
19共有1658个 频率为:0.01658
20共有1356个 频率为:0.01356
21共有973个 频率为:0.00973
22共有609个 频率为:0.00609
23共有322个 频率为:0.00322
总频率为:1.00000
总体中各数据的权重为:
0.08304 0.07958 0.07612 0.07266 0.06920 0.06574 0.06228 0.05882 0.05536 0.05190 0.04844 0.04498 0.04152 0.00000 0.03460 0.03114 0.02768 0.02422 0.02076 0.01730 0.01384 0.01038 0.00692 0.00346
总体中各数据的权重为:
0.08664 0.08303 0.07942 0.07581 0.07220 0.06859 0.06498 0.06137 0.05776 0.05415 0.05054 0.04693 0.00000 0.00000 0.03610 0.03249 0.02888 0.02527 0.02166 0.01805 0.01444 0.01083 0.00722 0.00361
总体中各数据的权重为:
0.09302 0.08915 0.08527 0.08140 0.07752 0.00000 0.06977 0.06589 0.06202 0.05814 0.05426 0.05039 0.00000 0.00000 0.03876 0.03488 0.03101 0.02713 0.02326 0.01938 0.01550 0.01163 0.00775 0.00388
总体中各数据的权重为:
0.10127 0.09705 0.09283 0.00000 0.08439 0.00000 0.07595 0.07173 0.06751 0.06329 0.05907 0.05485 0.00000 0.00000 0.04219 0.03797 0.03376 0.02954 0.02532 0.02110 0.01688 0.01266 0.00844 0.00422
总体中各数据的权重为:
0.00000 0.10798 0.10329 0.00000 0.09390 0.00000 0.08451 0.07981 0.07512 0.07042 0.06573 0.06103 0.00000 0.00000 0.04695 0.04225 0.03756 0.03286 0.02817 0.02347 0.01878 0.01408 0.00939 0.00469
总体中各数据的权重为:
0.00000 0.11735 0.11224 0.00000 0.10204 0.00000 0.09184 0.00000 0.08163 0.07653 0.07143 0.06633 0.00000 0.00000 0.05102 0.04592 0.04082 0.03571 0.03061 0.02551 0.02041 0.01531 0.01020 0.00510
总体中各数据的权重为:
0.00000 0.12707 0.12155 0.00000 0.11050 0.00000 0.09945 0.00000 0.08840 0.00000 0.07735 0.07182 0.00000 0.00000 0.05525 0.04972 0.04420 0.03867 0.03315 0.02762 0.02210 0.01657 0.01105 0.00552
总体中各数据的权重为:
0.00000 0.13295 0.12717 0.00000 0.11561 0.00000 0.10405 0.00000 0.09249 0.00000 0.08092 0.07514 0.00000 0.00000 0.05780 0.05202 0.00000 0.04046 0.03468 0.02890 0.02312 0.01734 0.01156 0.00578
总体中各数据的权重为:
0.00000 0.00000 0.14667 0.00000 0.13333 0.00000 0.12000 0.00000 0.10667 0.00000 0.09333 0.08667 0.00000 0.00000 0.06667 0.06000 0.00000 0.04667 0.04000 0.03333 0.02667 0.02000 0.01333 0.00667
总体中各数据的权重为:
0.00000 0.00000 0.15278 0.00000 0.13889 0.00000 0.12500 0.00000 0.11111 0.00000 0.09722 0.09028 0.00000 0.00000 0.06944 0.06250 0.00000 0.04861 0.00000 0.03472 0.02778 0.02083 0.01389 0.00694
总体中各数据的权重为:
0.00000 0.00000 0.17460 0.00000 0.15873 0.00000 0.00000 0.00000 0.12698 0.00000 0.11111 0.10317 0.00000 0.00000 0.07937 0.07143 0.00000 0.05556 0.00000 0.03968 0.03175 0.02381 0.01587 0.00794
总体中各数据的权重为:
0.00000 0.00000 0.18182 0.00000 0.16529 0.00000 0.00000 0.00000 0.13223 0.00000 0.11570 0.10744 0.00000 0.00000 0.08264 0.07438 0.00000 0.05785 0.00000 0.00000 0.03306 0.02479 0.01653 0.00826
总体中各数据的权重为:
0.00000 0.00000 0.00000 0.00000 0.20202 0.00000 0.00000 0.00000 0.16162 0.00000 0.14141 0.13131 0.00000 0.00000 0.10101 0.09091 0.00000 0.07071 0.00000 0.00000 0.04040 0.03030 0.02020 0.01010
总体中各数据的权重为:
0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.20253 0.00000 0.17722 0.16456 0.00000 0.00000 0.12658 0.11392 0.00000 0.08861 0.00000 0.00000 0.05063 0.03797 0.02532 0.01266
总体中各数据的权重为:
0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.24242 0.00000 0.21212 0.00000 0.00000 0.00000 0.15152 0.13636 0.00000 0.10606 0.00000 0.00000 0.06061 0.04545 0.03030 0.01515
总体中各数据的权重为:
0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.28000 0.00000 0.00000 0.00000 0.20000 0.18000 0.00000 0.14000 0.00000 0.00000 0.08000 0.06000 0.04000 0.02000
总体中各数据的权重为:
0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.34146 0.00000 0.00000 0.00000 0.24390 0.00000 0.00000 0.17073 0.00000 0.00000 0.09756 0.07317 0.04878 0.02439
总体中各数据的权重为:
0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.37037 0.00000 0.00000 0.25926 0.00000 0.00000 0.14815 0.11111 0.07407 0.03704
总体中各数据的权重为:
0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.50000 0.00000 0.00000 0.00000 0.00000 0.00000 0.20000 0.15000 0.10000 0.05000
总体中各数据的权重为:
0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.58824 0.00000 0.00000 0.00000 0.00000 0.00000 0.23529 0.00000 0.11765 0.05882
总体中各数据的权重为:
0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.57143 0.00000 0.28571 0.14286
总体中各数据的权重为:
0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.66667 0.00000 0.33333 0.00000
总体中各数据的权重为:
0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 0.00000 1.00000 0.00000 0.00000 0.00000
从总体中抽取的24个样本的下标索引为:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
抽样次数为:24
参考C++: Sampling from discrete distribution without replacement该问题下Aleph0的回答中Easier answer的代码,实现不放回抽样,该回答参考自Faster weighted sampling without replacement回答,具体原理参考自Weighted random sampling with a reservoir这篇论文。简单来说,是把权重值作为指数,均匀分布随机数作为底相当于给权重增加了均匀随机扰动,按照扰动后的权重值从大到小排序,并扩展到索引,然后根据样本数量从前往后直接取数。
测试代码:
//随机扰动方案
#include
#include
#include
#include
#include
#include
int main()
{
//随机数引擎采用默认引擎
std::default_random_engine rng;
//随机数引擎采用设备熵值保证随机性
auto gen = std::mt19937{ std::random_device{}() };
std::vector<double> wts(24); //存储权重值
std::vector<int> in(24); //存储总体
std::map<int, int> count; //输出计数
std::uniform_real_distribution<double> u(0.0, 1.0); //均匀分布
std::vector<double> vals;
std::vector<std::pair<int, double>> valsWithIndices;
std::vector<size_t> samples; //样本
int sampleCount = 0; //抽样次数计数
int sampleSize = 24; //抽取样本的数量
int sampleTimes = 10000; //抽取样本的次数
//权重赋值
for (int i = 0; i < 24; i++)
{
wts.at(i) = 48 - 2 * i;
}
//总体赋值并输出
std::cout << "总体为24个:" << std::endl;
//赋值
for (int i = 0; i < 24; i++)
{
in.at(i) = i + 1;
std::cout << in.at(i) << " ";
}
std::cout << std::endl << std::endl;
//==========抽样测试==========
//每次仅抽取1个下标索引
for (size_t i = 0; i < sampleTimes; i++)
{
//把权重值作为指数,均匀分布随机数作为底
//相当于给权重增加了均匀随机扰动
for (auto iter : wts)
{
vals.push_back(std::pow(u(gen), 1. / iter));
}
//按照扰动后的权重值从大到小排序,并扩展到索引
for (size_t iter = 0; iter < vals.size(); iter++)
{
valsWithIndices.emplace_back(iter, vals[iter]);
}
std::sort(valsWithIndices.begin(), valsWithIndices.end(), [](auto x, auto y) {return x.second > y.second; });
//样本大小sampleSize设置为1
samples.push_back(valsWithIndices[0].first);
//对样本计数
count[samples.at(0)] += 1;
vals.clear(); //清空集合,为下次抽样做准备
valsWithIndices.clear(); //清空集合,为下次抽样做准备
samples.clear(); //清空样本集合,为下次抽样做准备
}
double sum = 0.0; //用于概率求和
//输出抽样结果
std::cout << "总共抽样" << sampleTimes << "次," << "各下标的频数及频率为:" << std::endl;
for (size_t i = 0; i < 24; i++)
{
std::cout << i << "共有" << count[i] << "个 频率为:" << count[i] / double(sampleTimes) << std::endl;
sum += count[i] / double(sampleTimes);
}
std::cout << "总频率为:" << sum << std::endl << std::endl; //输出总概率
//==========抽样测试==========
//==========实际抽样==========
//把权重值作为指数,均匀分布随机数作为底
//相当于给权重增加了均匀随机扰动
for (auto iter : wts)
{
vals.push_back(std::pow(u(gen), 1. / iter));
}
//按照扰动后的权重值从大到小排序,并扩展到索引
for (size_t iter = 0; iter < vals.size(); iter++)
{
valsWithIndices.emplace_back(iter, vals[iter]);
}
std::sort(valsWithIndices.begin(), valsWithIndices.end(), [](auto x, auto y) {return x.second > y.second; });
//抽样
for (auto iter = 0; iter < sampleSize; iter++)
{
samples.push_back(valsWithIndices[iter].first);
}
sampleCount += 1; //抽样次数增加1
//输出抽样结果
std::cout << "从总体中抽取的" << sampleSize << "个样本的下标索引为:" << std::endl;
for (auto iter : samples)
{
std::cout << iter << " ";
}
std::cout << std::endl;
//输出抽样次数
std::cout << "抽样次数为:" << sampleCount << std::endl;
vals.clear(); //清空集合,为下次抽样做准备
valsWithIndices.clear(); //清空集合,为下次抽样做准备
samples.clear(); //清空样本集合,为下次抽样做准备
//==========实际抽样==========
std::cin.get(); //保留控制台窗口
return 0;
}
输出结果:
总体为24个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
总共抽样10000次,各下标的频数及频率为:
0共有822个 频率为:0.0822
1共有752个 频率为:0.0752
2共有714个 频率为:0.0714
3共有641个 频率为:0.0641
4共有697个 频率为:0.0697
5共有646个 频率为:0.0646
6共有585个 频率为:0.0585
7共有536个 频率为:0.0536
8共有545个 频率为:0.0545
9共有499个 频率为:0.0499
10共有480个 频率为:0.048
11共有449个 频率为:0.0449
12共有363个 频率为:0.0363
13共有365个 频率为:0.0365
14共有347个 频率为:0.0347
15共有328个 频率为:0.0328
16共有274个 频率为:0.0274
17共有236个 频率为:0.0236
18共有211个 频率为:0.0211
19共有172个 频率为:0.0172
20共有139个 频率为:0.0139
21共有99个 频率为:0.0099
22共有60个 频率为:0.006
23共有40个 频率为:0.004
总频率为:1
从总体中抽取的24个样本的下标索引为:
10 9 5 7 4 13 2 14 21 8 3 11 6 19 0 12 16 1 17 18 15 20 22 23
抽样次数为:1
随机扰动方案十分巧妙,就是将权重值或者说概率作为排序的依据,但是如果直接根据权重排序,则会造成权重大的永远在前面,因此要对其增加一个均匀随机扰动,保证了随机性,又使得权重大的更容易被抽取到。