如何产生随机数,使它们的总和等于给定的数字?

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&lt;vector&lt;int&gt;&gt; 并使用所有可能的数字组合来解决输入标准,然后每次需要新的解决方案时,它都会随机选择其中一个并将数字打乱顺序(从而选择组合的排列)。

它有点重——也许不适合你在我开始写它之后提到的实际用途;-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 &lt;random&gt; 标头。

我们只是确保没有剩余的数字超过 MAX (14) 并且最后达到 MAXminNum 是奇怪的部分,这是由于它的进展方式。它从零开始并根据需要逐步上升(std::max 的第二部分正在计算如果我们得到 6s 剩下的部分需要什么),但我们不能让它超过 maxNum。如果存在minNum,我愿意接受更简单的计算方法。

【讨论】:

不幸的是,这会产生非常倾斜的分布。尝试运行 MAX=50, LINES=8, maxNum=10 万次,看看我在说什么。 @AlbertoSchiabel,感谢您指出这一点。没有什么好的可以立即解决(在花了一些时间再次弄清楚问答之后),但我确实发现并修复了一个“悲剧性错误”,这很有趣。 实际上,纯属巧合,我在reservoir sampling 上进行了讨论。在我看来,这种解决方案可能会通过这些技术进行调整,以产生公正的结果。我不能肯定地说,但感觉可以减少问题。【参考方案5】:

由于您知道需要多少个数字,因此可以根据给定分布生成它们,但无需其他条件,存储它们,计算实际总和,然后按比例放大/缩小它们以获得所需的总和。

【讨论】:

嗨,维克多,您能举个例子吗?

以上是关于如何产生随机数,使它们的总和等于给定的数字?的主要内容,如果未能解决你的问题,请参考以下文章

随机产生 指定的个数和指定的总和的数字

c语言中如何产生1或0随机数

获取加起来等于给定数字的所有可能总和

数组中的两个不同数字,它们的和等于给定值

将给定的一组数字 N 分成两组,以使它们的总和之差最小?

.net 下用C#产生一个永不重复10位随机数