如何加快计算所有可能的多个项目分布的算法?

时间:2014-10-29 21:55:59

标签: c# algorithm recursion

我想计算许多项目的所有可能(使用某个步骤)分布。总和必须加起来1。 我的第一个方法如下:

var percentages = new List<double>(new double[3]);

while (Math.Abs(percentages.Last() - 1.0) > 0.01) 
{
    Increment(percentages, 0);
    if (Math.Abs(percentages.Sum() - 1.0) < 0.01)
    {
        percentages.ForEach(x => Console.Write("{0}\t", x));
        Console.WriteLine();
    }
}

private void Increment(List<double> list, int i)
{
    if (list.Count > i)
    {
        list[i] += 0.1;
        if (list[i] >= 1)
        {
            list[i] = 0;
            Increment(list, ++i);
        }
    }
}

哪个输出想要的结果:

1 0 0
0.9 0.1 0
0.8 0.2 0
0.7 0.3 0
0.6 0.4 0
0.5 0.5 0
0.4 0.6 0
0.3 0.7 0
0.2 0.8 0
0.1 0.9 0
0 1 0
0.9 0 0.1 ..

我想知道如何加快计算速度,因为项目数量会变得非常大(> 20)。 显然,我计算了很多分布只是为了抛弃它们,因为它们不加1。 有什么想法吗?

4 个答案:

答案 0 :(得分:1)

这适用于3组数字:

var query =
    from x in Enumerable.Range(0, 11)
    from y in Enumerable.Range(0, 11 - x)
    let z = 10 - x - y
    select new [] { x / 10.0, y / 10.0, z / 10.0 };

var percentages = query.ToList();

percentages
    .ForEach(ps => Console.WriteLine(String.Join("\t", ps)));

这是一个通用版本:

Func<int, int[], int[][]> generate = null;
generate = (n, ns) =>
    n == 1
        ? new int[][]
            {
                ns
                    .Concat(new [] { 10 - ns.Sum() })
                    .ToArray()
            }
        : Enumerable
            .Range(0, 11 - ns.Sum())
            .Select(x =>
                ns.Concat(new [] { x }).ToArray())
            .SelectMany(xs => generate(n - 1, xs))
            .ToArray();

var elements = 4;

var percentages =
    generate(elements, new int[] { })
        .Select(xs => xs.Select(x => x / 10.0).ToArray())
        .ToList();

只需更改elements值即可获得内部数组的元素数。

答案 1 :(得分:0)

我会把它翻出来。跟踪剩余部分,并且只增加到剩余部分。您还可以通过将最后一个元素设置为唯一可以工作的值来加快速度。这样,您看到的每个组合都将是可打印的。

如果你以这种方式组织事情,那么你可能会发现将打印放在递归函数中会很好。

我不用C#编程,但它可能看起来像这样:

var percentages = new List<double>(new double[3]);

PrintCombinations(percentages, 0, 1.0);

private void PrintCombinations(List <double> list, int i, double r) {
    double x = 0.0;
    if (list.Count > i + 1) {
        while (x < r + 0.01) {
            list[i] = x;
            PrintCombinations(list, i+1, r-x);
        }
    }
    else {
        list[i] = r;
        percentages.ForEach(x => Console.Write("{0}\t", x));
        Console.WriteLine();
    }
}

(不可否认,这确实将组合放在一个不同的顺序中。修复它留作练习......)

答案 2 :(得分:0)

如果通过&#39;发布&#39;你的意思是3个数字的总和,步长为0.1,加上1.0&#39;,这个相当直接的方法怎么样:

for (decimal i = 0; i< 1; i+=0.1m)
    for (decimal j = 0; j < 1 - i; j+=0.1m)
    {
        Console.WriteLine(i + " " + j  + " " + (1 - i - j)  );
    }

答案 3 :(得分:0)

冒着重复工作的风险,这里的版本与其他答案基本相同。但是,我已经写过了,所以我不妨分享一下。我还要指出一些可能对OP有重要影响的差异:

static void Main(string[] args)
{
    Permute(1, 0.1m, new decimal[3], 0);
}

static void Permute(decimal maxValue, decimal increment, decimal[] values, int currentValueIndex)
{
    if (currentValueIndex == values.Length - 1)
    {
        values[currentValueIndex] = maxValue;
        Console.WriteLine(string.Join(", ", values));
        return;
    }

    values[currentValueIndex] = 0;

    while (values[currentValueIndex] <= maxValue)
    {
        Permute(maxValue - values[currentValueIndex], increment, values, currentValueIndex + 1);
        values[currentValueIndex] += increment;
    }
}

注意:

  • 我在这里使用decimal类型。在这种特殊情况下,它可以避免像原始代码那样需要基于epsilon的检查。
  • 我更喜欢使用string.Join(),而不是多次拨打Console.Write()
  • 同样在这种情况下,使用List<T>似乎没有用,所以我的实现使用数组。

但我承认,它的基本算法是一样的。