如何产生随机数,使它们的总和等于给定的数字?
Posted
技术标签:
【中文标题】如何产生随机数,使它们的总和等于给定的数字?【英文标题】:How to produce random numbers so that their sum is equal to given number? 【发布时间】:2014-01-15 00:39:57 【问题描述】:我想产生 X 个随机数,每个随机数来自区间 <0; Y>
(给定 Y
作为每个数字的最大值),但有限制,这些数字的总和必须等于 Z
。
示例:
5个随机数,每个最多6个,总和必须等于14,例如:0, 2, 6, 4, 2
是否已经有可以做类似事情的 C/C++ 函数?
就我个人而言,我只能想出一些丑陋的 if-else-constructs。
【问题讨论】:
也许使用std::uniform_int_distribution
,跟踪剩余数字的限制,并用std::shuffle
结束它,所以第一个不会比最后一个更特别。
@chris 他不关心重复,从他的示例输出来看
@BWG,不考虑重复。限制跟踪是在你得到 6 6 开始的情况下。
@chris 他们没有被考虑在内。因为他不在乎。那么如果他以6-6开始呢?它生成一个随机序列,因此 6-6 是可接受的开始。他的问题没有提到分布模式。
@user2685786 在这种情况下,您可能需要更新您的问题,因为您说的是“Z 的最大值”。听起来你的意思是总数必须是“正好 Z”。
【参考方案1】:
由于您不需要生成的序列是统一的,这可能是可能的解决方案之一:
#include <iostream>
#include <vector>
#include <cstdlib>
int irand(int min, int max)
return ((double)rand() / ((double)RAND_MAX + 1.0)) * (max - min + 1) + min;
int main()
int COUNT = 5, // X
MAX_VAL = 6, // Y
MAX_SUM = 14; // Z
std::vector<int> buckets(COUNT, 0);
srand(time(0));
int remaining = MAX_SUM;
while (remaining > 0)
int rndBucketIdx = irand(0, COUNT-1);
if (buckets[rndBucketIdx] == MAX_VAL)
continue; // this bucket is already full
buckets[rndBucketIdx]++;
remaining--;
std::cout << "Printing sequence: ";
for (size_t i = 0; i < COUNT; ++i)
std::cout << buckets[i] << ' ';
它只是简单地将总和分成一堆桶,直到它消失:)
输出示例:Printing sequence: 4 4 1 0 5
【讨论】:
这是不好的随机性。至于以下测试:COUNT 5, MAX_VAL 5000 MAX_SUM 5000。你编程总是在 1000、1000、1000、1000、1000 附近编程,这是一个糟糕的随机数。【参考方案2】:注意:此解决方案是在问题指定“MAX SUM”参数时编写的,这意味着小于该数量的总和同样可以接受。该问题现在已根据 OP 的评论进行了编辑,他们表示累积总和必须实际达到该目标。我不会更新此答案,但显然它可以在最后一级递归中轻易丢弃较小的总数。
此解决方案一次性填充 vector<vector<int>>
并使用所有可能的数字组合来解决输入标准,然后每次需要新的解决方案时,它都会随机选择其中一个并将数字打乱顺序(从而选择组合的排列)。
它有点重——也许不适合你在我开始写它之后提到的实际用途;-P——但它产生了一个均匀加权分布,并且您可以轻松地做一些事情,例如保证在返回所有其他组合之前不会再次返回组合(带有支持的组合索引的混洗向量)。
#include <iostream>
#include <vector>
#include <algorithm>
using std::min;
using std::max;
using std::vector;
// print solutions...
void p(const vector<vector<int>>& vvi)
for (int i = 0; i < vvi.size(); ++i)
for (int j = 0; j < vvi[i].size(); ++j)
std::cout << vvi[i][j] << ' ';
std::cout << '\n';
// populate results with solutions...
void f(vector<vector<int>>& results, int n, int max_each, int max_total)
if (n == 0) return;
if (results.size() == 0)
for (int i = 0; i <= min(max_each, max_total); ++i)
results.push_back(vector<int>(2, i));
f(results, n - 1, max_each, max_total);
return;
vector<vector<int>> new_results;
for (int r = 0; r < results.size(); ++r)
int previous = *(results[r].rbegin() + 1);
int current_total = results[r].back();
int remaining = max_total - current_total;
for (int i = 0; i <= min(previous,min(max_each, remaining)); ++i)
vector<int> v = results[r];
v.back() = i;
v.push_back(current_total + i);
new_results.push_back(v);
results = new_results;
f(results, n - 1, max_each, max_total);
const vector<int>& once(vector<vector<int>>& solutions)
int which = std::rand() % solutions.size();
vector<int>& v = solutions[which];
std::random_shuffle(v.begin(), v.end() - 1);
return v;
int main()
vector<vector<int>> solutions;
f(solutions, 5, 6, 14);
std::cout << "All solution combinations...\n";
p(solutions);
std::cout << "------------------\n";
std::cout << "A few sample permutations...\n";
for (int n = 1; n <= 100; ++n)
const vector<int>& o = once(solutions);
for (int i = 0; i < o.size() - 1; ++i)
std::cout << o[i] << ' ';
std::cout << '\n';
【讨论】:
我只是想用X=1,000,000 Y=1,000,000 Z=1,000,000,000
运行它 =) 当我的思想实验完成初始填充步骤时,我会通知你。【参考方案3】:
#include<iostream>
#include <cstdlib> //rand ()
using namespace std;
void main()
int random ,x=5;
int max , totalMax=0 , sum=0;
cout<<"Enter the total maximum number : ";
cin>>totalMax;
cout<<"Enter the maximum number: ";
cin>>max;
srand(0);
for( int i=0; i<x ; i++)
random=rand()%max+1; //range from 0 to max
sum+=random;
if(sum>=totalMax)
sum-=random;
i--;
else
cout<<random<<' ';
cout<<endl<<"Reached total maximum number "<<totalMax<<endl;
我写了这个简单的代码 我使用 totalMax=14 和 max=3 对其进行了测试,它对我有用 希望这是你要求的
【讨论】:
无法保证此算法将在可接受的时间内终止。此外,它对序列中较早出现的数字给予不公平的权重(通过始终接受它们),从而使分布产生偏差。 我同意你的观点,我不应该在测试之前接受所有数字 这不能回答问题。无法保证该序列将加起来为特定值,而这正是 OP 所要求的。 事实上可以保证它不会加到特定值,因为它会在sum == totalMax
的情况下回溯。【参考方案4】:
LiHo 的回答看起来与我的第二个建议非常相似,所以我将保留它,但这里是第一个的示例。它可能可以改进,但它不应该有任何悲惨的错误。 Here's a live sample.
#include <algorithm>
#include <array>
#include <random>
std::random_device rd;
std::mt19937 gen(rd());
constexpr int MAX = 14;
constexpr int LINES = 5;
int sum;
int maxNum = 6;
int minNum;
std::array<int, LINES> nums;
for (int i = 0; i < LINES; ++i)
maxNum = std::min(maxNum, MAX - sum);
// e.g., after 0 0, min is 2 because only 12/14 can be filled after
int maxAfterThis = maxNum * (LINES - i - 1);
minNum = std::min(maxNum, std::max(minNum, MAX - sum - maxAfterThis));
std::uniform_int_distribution<> dist(minNum, maxNum);
int num = dist(gen);
nums[i] = num;
sum += num;
std::shuffle(std::begin(nums), std::end(nums), gen);
每次创建该分布可能会减慢它的速度(我不知道),但范围必须在构造函数中进行,我并不是说这些数字的分布情况如何。但是,逻辑非常简单。除此之外,它还使用了漂亮、闪亮的 C++11 <random>
标头。
我们只是确保没有剩余的数字超过 MAX
(14) 并且最后达到 MAX
。 minNum
是奇怪的部分,这是由于它的进展方式。它从零开始并根据需要逐步上升(std::max
的第二部分正在计算如果我们得到 6s 剩下的部分需要什么),但我们不能让它超过 maxNum
。如果存在minNum
,我愿意接受更简单的计算方法。
【讨论】:
不幸的是,这会产生非常倾斜的分布。尝试运行 MAX=50, LINES=8, maxNum=10 万次,看看我在说什么。 @AlbertoSchiabel,感谢您指出这一点。没有什么好的可以立即解决(在花了一些时间再次弄清楚问答之后),但我确实发现并修复了一个“悲剧性错误”,这很有趣。 实际上,纯属巧合,我在reservoir sampling 上进行了讨论。在我看来,这种解决方案可能会通过这些技术进行调整,以产生公正的结果。我不能肯定地说,但感觉可以减少问题。【参考方案5】:由于您知道需要多少个数字,因此可以根据给定分布生成它们,但无需其他条件,存储它们,计算实际总和,然后按比例放大/缩小它们以获得所需的总和。
【讨论】:
嗨,维克多,您能举个例子吗?以上是关于如何产生随机数,使它们的总和等于给定的数字?的主要内容,如果未能解决你的问题,请参考以下文章