【问题标题】:How to find what numbers in a set add up to another given number?如何找到一组中的哪些数字加起来另一个给定的数字?
【发布时间】:2010-11-15 05:02:42
【问题描述】:

这是我在使用会计系统时似乎遇到的一个问题。

我有一组交易,但它们的总和不等于会计部门认为应该的金额。他们不是在质疑数学,只是在质疑交易:p

是否有一种算法可以帮助我确定不应该包含集合中的哪些交易以使总和与给定金额匹配。

Given Set:  
2  
4  
5  
7

Given Sum Amount:
13

Result Set:
2
4
7

编辑: 集合中的事务少于 100 个。有没有人有一个 C# 示例,因为Solving the NP-complete problem in XKCD 问题上没有一个示例?

伙计,我应该获得计算机科学学位。

【问题讨论】:

  • 这里的服务员也提出了同样的问题:xkcd.com/287,这里有一堆潜在的解决方案:stackoverflow.com/questions/141779/…
  • 正如其他人所说,这是无法做到的。但是,如果您可以找到一种方法来根据特定于您的数据的内容减少搜索空间,那么您可能会做得更好。但是我们都没有足够的信息来帮助解决这个问题......而且它可能不容易提供给我们,即使假设你被允许这样做。
  • 布赖恩,我认为没有人说它不能完成,只是会很难(但我想我知道你想说什么)。
  • @erich 实际上,想出一个可以找到答案的算法并不难。只是可能需要很长很长时间。几乎可以说该解决方案实施起来很简单。
  • @tim:我的意思是“难”是一个双关语,不是很难找到算法,而是找到解决方案。 NPC - NP-Hard - 你明白我的意思。

标签: c# algorithm np-complete np-hard


【解决方案1】:

这是Knapsack Problem,它是NP-Complete。除了小的输入集之外,你不会轻易地用任何东西来准确地解决它。对于任何体面大小的问题集,它都是需要解决的宇宙生命周期问题之一。

也就是说,那里有遗传算法背包求解器。

【讨论】:

    【解决方案2】:

    这是the knapsack problem 的一个版本。它是 NP 完全的,所以你不会得到一个好的一般答案。你的交易集有多大?是像你展示的那样是 5,还是更像是 500?

    【讨论】:

      【解决方案3】:

      这是Subset Sum 问题,即NP-Complete。但这并不意味着没有找到子集总和的算法。

      【讨论】:

      • +1 提供比其他人更准确的答案。子集和问题是背包问题的一个特例。
      • 但这背包问题。他不只是询问子集是否存在,而是确切地询问该子集是什么。
      • 我的理解是 Subset Sum 是完全匹配的,而更广义的 Knapsack 是找到一个最大值。
      【解决方案4】:

      正如上面的成员所说,这是子集和问题(或背包问题)。 但是,说它不能有效地完成并不是很准确。可以,只是不行 在多项式时间内。使用动态编程的解决方案实际上非常简单 和递归(在伪多项式时间内)。

      给定整数 [a_1, ... ,a_n] 和一个数字 T,

      定义数组 S[i,k] 来表示两者之间是否存在元素子集 a_1, ... a_i 加起来为 k。 (这是一个二进制矩阵)。

      然后我们可以如下定义递归关系:

      S[i,k] = S[i-1,k] 或 S[i-1,k-a_j]

      换句话说,这意味着我们要么使用元素 a_i,要么不使用。 答案将位于 S[n,T]。

      构造矩阵 S 的工作量是多少? 好吧,S 有 n*T 个元素。要计算每个元素, 我们必须做 O(1) 的工作。所以完整的运行 时间是 O(n*T)。

      现在,我似乎已经证明了 P=NP,因为这 似乎是多项式时间算法。然而,记住 我们用二进制测量输入大小,所以 T = 2^p 对于一些 p.

      我认为没有人会说上述解决方案,当 实施得当是不合理的。事实上,对于许多 合理的问题大小,它会表现得非常出色。

      另外,有一些启发式方法可以解决这个问题,但我是 不是该领域的专家。

      【讨论】:

        【解决方案5】:

        好的。很多人都给出了问题的名称,并提到了 NP 有多难。总的来说,它们是正确的。但是,您有一个非常具体的案例需要解决。要问的第一个问题是您是否认为您的 100 笔交易接近正确。你有一些总数(“你的”总数)。他们有一些总数。 (“真实”总数)。因此,您的一些交易是虚假的。如果您怀疑那里只有少数虚假交易,那么这还不错。例如,让我们考虑只有一个虚假交易的情况。在这种情况下,我们只需要检查 100 个不同的数字。如果有 2 个伪造的反式,那么您正在查看 100*99 的支票,而 4 个伪造的反式将变得疯狂,几乎有 100,000,000 步。不过,如果您所做的只是添加一些 int,那也不算太糟糕。

        另一个可能的捷径是检查数据的性质(顺便说一句,是否可以发布 100 个“数字”和预期的总和?)。你的总和比实际总和高多少?是否有任何值如此之大以至于消除它们会使您的总和突然低于实际总和?如果是这样,您就知道这些值不可能是虚假的。例如,在您的示例中,绝对需要 7。

        【讨论】:

        • 谢谢。我昨天在想如何减少通勤回家的子集。例如,可以将要使用的集合缩减为一组交易,其金额小于原始集合总数与预期总数之间的差值。然后在缩减集中找到总和与该差值匹配的交易。
        【解决方案6】:
                bool bBreak = false;
                int iEnd = 13;
                ArrayList ar1 = new ArrayList();
                ar1.Add(2);
                ar1.Add(4);
                ar1.Add(5);
                ar1.Add(7);
        
                String s1 = " ";
                foreach (int i in ar1)
                {
                    if (i == iEnd)
                    {
                        s1 = "Result = " + i;
                        bBreak = true;
                    }
                    if (bBreak) break;
                    ArrayList ar2 = new ArrayList();
                    ar2.AddRange(ar1);
                    ar2.Remove(i);
                    foreach (int j in ar2)
                    {
                        if ((i + j) == iEnd)
                        {
                            s1 = "Results = " + i + ", " + j;
                            bBreak = true;
                        }
        
                        if (bBreak) break;
                        ArrayList ar3 = new ArrayList();
                        ar3.AddRange(ar2);
                        ar3.Remove(j);
                        foreach (int k in ar3)
                        {
                            if (bBreak) break;
                            if ((i + j + k) == iEnd)
                            {
                                s1 = "Results = " + i + ", " + j + ", " + k;
                                bBreak = true;
                            }
                        }
                    }
                }
                Console.WriteLine(s1);
        

        【讨论】:

          【解决方案7】:

          是的,这是可能的。不确定这个帖子是否仍然打开。但是您会想要执行 Excel Solver 加载项。发布所有数字,并在相邻单元格上加上 1。然后输入所需的输出编号..然后所有使用的数字将保持其相邻的“1”,而未使用的将变为“0”。然后做一个过滤公式,只列出旁边有“1”的那些。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2019-08-13
            • 1970-01-01
            • 1970-01-01
            • 2017-07-10
            • 1970-01-01
            • 2022-11-10
            • 1970-01-01
            相关资源
            最近更新 更多