【问题标题】:How to optimize this code? (Maybe with LINQ?)如何优化这段代码? (也许使用 LINQ?)
【发布时间】:2014-03-19 20:41:56
【问题描述】:

我有这个代码:

ArrayList arrayA = new ArrayList();
ArrayList arrayB = new ArrayList();
ArrayList arrayC = new ArrayList();
ArrayList arrayD = new ArrayList();
ArrayList arrayE = new ArrayList();
ArrayList arrayF = new ArrayList();
ArrayList arrayG = new ArrayList();
ArrayList arrayH = new ArrayList();
ArrayList arrayI = new ArrayList();
ArrayList arrayJ = new ArrayList();

int n = 0;

for (decimal a = 0.1m; a <= 100m; a += 0.1m)
{
    for (decimal b = 100m - a; b > 0m; b -= 0.1m)
    {
        for (decimal c = 100m - b; c > 0m; c -= 0.1m)
        {
            for (decimal d = 100m - c; d > 0m; d -= 0.1m)
            {
                for (decimal e = 100m - d; e > 0m; e -= 0.1m)
                {
                    for (decimal f = 100m - e; f > 0m; f -= 0.1m)
                    {
                        for (decimal g = 100m - f; g > 0m; g -= 0.1m)
                        {
                            for (decimal h = 100m - g; h > 0m; h -= 0.1m)
                            {
                                for (decimal i = 100m - h; i > 0m; i -= 0.1m)
                                {
                                    for (decimal j = 100m - i; j > 0m; j -= 0.1m)
                                    {
                                        if ((a + b + c + d + e + f + g + h + i + j) == 100)
                                        {
                                            ++n;
                                            arrayA.Add(a);
                                            arrayB.Add(b);
                                            arrayC.Add(c);
                                            arrayD.Add(d);
                                            arrayE.Add(e);
                                            arrayF.Add(f);
                                            arrayG.Add(g);
                                            arrayH.Add(h);
                                            arrayI.Add(i);
                                            arrayJ.Add(j);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

任何想法如何优化这个?这需要很长时间才能执行。

基本上需要知道0.1到100之间的10个数字的组合数,其中它们的总和正好是100。(我需要知道组合的数量和组合本身)

提前致谢

【问题讨论】:

  • 你为什么要使用ArrayList?你有没有考虑过你要尝试多少种组合?您惊讶需要很长时间吗?
  • 不,我并不感到惊讶。为什么我使用 ArrayList?我不知道,只是因为是的,如果你有任何想法告诉我,因为我今天想不起来了,谢谢
  • 您的操作看起来内存不足。您将a 数百万次添加到arrayA 中真的是有意的吗?删除数组添加并让它计算n。完成。
  • 我只需要知道总和等于100的组合和组合的数量。使用数组列表,我可以通过每个数组列表中的记录ID看到,每个ID代表一个组合ArrayLists,但我确信有更好的方法......
  • 另外,如果我没记错的话(已经有一段时间了......),你在 4.02 * 10^2568 测试附近。

标签: c# algorithm optimization


【解决方案1】:

我认为这个问题没有直接的解决方案,尤其是对于这种类型的数字。但这里有一个非常通用的想法可以帮助您开发自己的算法:

  1. 了解Gosper's Hack。它可以生成复杂度为 O(n) 的组合。
  2. 您将意识到您需要生成 C(1000,10) 组合,这不能直接使用原始数据类型来实现。为了克服这个问题,您可以实现自己的类,该类可以表示具有必要操作(如添加和移位等)的大位序列。(或者您可以在标准库中找到类似的类并对其进行扩展。)

我知道这不是一件容易的事,我不想自己实现它。但它会给你一个 O(n) 的时间复杂度,你只需在线性时间内迭代组合,使用该位序列以步骤 0.1 选择 0.1 到 100 之间的数字,并检查它们的总和是否为 100。然后添加位序列到最终的 ArrayList。

在 Ronan Thibaudau 的警告之后编辑:很抱歉我直接跳入 Java,但我认为您可以在自己喜欢的语言/框架中找到相应的方法。

又一次修改:现在帖子与语言无关。想法是完全一样的……我要说的是;如果您正在查看大型组合的此类问题,则此想法可以为您提供 O(n) 时间复杂度和 O(n) 内存需求。这实际上保证了它会在可行的时间内运行,在给定足够大的 n 的情况下消耗可行的内存量。

自行修改:我还在考虑,必须进行更正。 (我保留了有些错误的算法分析)

Gosper's Hack 肯定会给您 O(n) 的时间复杂度来生成组合,这对于获得可接受的优化实现至关重要。但是,由于您必须创建自己的类来表示位序列,因此您必须实现自己的移位器、加法器、ands 和诸如此类的东西(即,您不能直接使用您的硬件进行这些操作)。为此,您将需要另一个 n 循环。 这可能会使您的算法复杂度 O(n2)

绝对不如 O(n),但仍应满足您的执行时间需求。

【讨论】:

    【解决方案2】:

    如果您真的想优化建议的算法,我会从省略不必要的循环开始,如果总和 >= 100,则继续没有意义,例如:

    for (decimal a = 0.1m; a <= 100m; a += 0.1m)
    {
        for (decimal b = 100m - a; b > 0m; b -= 0.1m)
        {
            if (a + b >= 100m)
                 continue;
    
                 for (decimal c = 100m - b; c > 0m; c -= 0.1m)
                 {
                     if (a + b + c >= 100m)
                         continue;
                     ...
    

    无论如何,您永远无法存储所有组合。例如,如果您只有三个数字,则输出将包含 1000^2 个结果。您可以任意选择第一个数字(0.1 .. 100,因此您有 1000 个选择),与第二个相同,例如,我将选择 0.5 和 42 - 第三个我已修复,57.5 - 所以它是 1000 * 1000 * 1 的好成绩。现在将其扩展到 10 个数字。

    【讨论】:

      【解决方案3】:

      我认为这是使用 LINQ 执行此操作的最佳方法:

      var n = 1000;
      var query =
          from a in Enumerable.Range(1, n)
          from b in Enumerable.Range(1, n - a)
          from c in Enumerable.Range(1, n - a - b)
          from d in Enumerable.Range(1, n - a - b - c)
          from e in Enumerable.Range(1, n - a - b - c - d)
          from f in Enumerable.Range(1, n - a - b - c - d - e)
          from g in Enumerable.Range(1, n - a - b - c - d - e - f)
          from h in Enumerable.Range(1, n - a - b - c - d - e - f - g)
          from i in Enumerable.Range(1, n - a - b - c - d - e - f - g - h)
          let j = n - a - b - c - d - e - f - g - h - i
          where j >= 1
          select new { a, b, c, d, e, f, g, h, i, j };
      

      我选择使用int 并将数字乘以10,而不是使用decimal0.1 增量。

      现在运行 1,000 似乎需要很长时间。

      所以我开始用较小的数字运行它并得到以下结果:

      10  1
      11  10
      12  55
      13  220
      14  715
      15  2002
      16  5005
      17  11440
      18  24310
      19  48620
      20  92378
      

      这是n & 返回的组合数。

      原来这个进程是C(n - 1, n - 10)。所以插入n = 1000 我得到了 2,634,095,604,619,700,000,000 种组合。

      现在,在我的计算机上,如果我使用 n = 30 运行,计算 10,015,005 个组合需要 11.551 秒。

      如果您对此进行数学计算,假设每年有 365.25 天,那么您会得出计算 n = 1000 需要 96,271,110 年的数字。

      即使是“深度思考”在计算 42 时也更快。祝你好运。

      【讨论】:

        【解决方案4】:

        这是一个答案(为简单起见,从 1 到 1000 而不是 0.1 到 100,只需将所有项目除以 10 即可得到 0.1 到 100 的值)

        首先,我们将使用生成器枚举和惰性操作符来避免内存问题,所有内容都会以 1 比 1 的方式流式传输到管道中,因此在代码运行时应该修复内存

        var kitems = Enumerable.Range(1,1000);
        var q = from a in kitems
                from b in kitems
                from c in kitems
                from d in kitems
                from e in kitems
                from f in kitems
                from g in kitems
                from h in kitems
                from i in kitems
                from j in kitems
                where a+b+c+d+e+f+g+h+i+j == 1000
                select new {a,b,c,d,e,f,g,h,i,j};
        // Since everything is lazy evaluated, the 10 first results are near immediate (nothing past those is evaluate, this is good for testing, remove the take operator if you want the full dataset)
        foreach(var item in q.Take(10))
        {
            // Do whatever you want with the result here
        }
        

        【讨论】:

        • 这是什么语言?是什么让您认为这是优化的? where a+b+c+d+e+f+g+h+i+j == 100 是如何计算的?
        • 这是在 C# 中,正是他所要求的(而您在 java 中回答,这不是 OP 使用的)。没有人说这是优化的,但它确实是平坦的内存使用(在管道中所有东西都是惰性评估的,1 逐 1 项,没有临时列表或类似的东西,使用虚拟 0 内存,当前无限制地运行它,一直在运行一段时间,这里的内存使用为 0)
        • 另外,如果您是投票反对的人,您可能需要重新考虑,因为虽然您可能不知道 C# 和 linq,但 OP 实际上特别询问是否可能有 linq 解决方案(这就是我提供)。
        • 它不是开场问题的优化变体。它同样或更多的努力。如果他想知道组合的数量,那么他需要计算整个集合,使用此 LINQ 语句将花费更长的时间。
        • 这要花更长的时间吗?原来的可能会耗尽他的记忆,而这个不会。
        【解决方案5】:

        让我们推理整数(1 到 1000,总和 1000)。令 C(K, N) 为总共 N 个中涉及 K 个变量的组合数。

        对于单个变量,我们有 1 种可能性(a=1000),即 C(1, 1000)=1。更一般地说,C(1, N)=1。

        对于两个变量,我们有 999 种组合(a=1, b=999 到 a=999, b=1),我们看到 C(2, 1000)= C(1, 999) + C(1, 998) + C(1, 997) + ... C(1, 1)。更一般地说,C(2, N) = N-1。

        对于三个变量,我们有 C(2, 999) 与 a=1 的组合,C(2, 998) 与 a=2... 和 C(2, 2) 与 a=998。由三角数公式,C(3, N) = N.(N-1)/2。

        对于四个变量,我们有 C(3, 999) 组合与 a=1、C(3, 998) 与 a=2... 和 C(3, 3) 与 a=997。由四面体数公式C(4, N)= N.(N-1).(N-2) / 6.

        以此类推(这只是帕斯卡三角形),直到:

        C(10, N) = N.(N-1).(N-2).(N-3).(N-4).(N-4).(N-6).(N -7).(N-8).(N-9) / 9!

        C(10, 1000) = 2634095604619702128324000

        没有希望通过枚举来计算这个天文数字!

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-02-06
          • 2013-02-15
          • 2016-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多