从给定数字中,确定其产品为原始数字的三个紧密数字

时间:2011-05-05 19:47:29

标签: algorithm math language-agnostic numbers

我有一个号码n,我希望找到三个号码,其产品为n,但尽可能彼此接近。也就是说,如果n = 12那么我希望得到3,2,2,而不是6,1,2。

另一种想到它的方法是,如果n是长方体的体积,那么我想找到两边的长度,以便使长方体尽可能像立方体一样(也就是说,长度尽可能相似)。这些数字必须是整数。

我知道不太可能有一个完美的解决方案,而且我很乐意使用能够在大多数时候给出一个好答案的东西,但是我想不出去想出这个算法。有什么想法吗?

5 个答案:

答案 0 :(得分:11)

这是我的第一个算法草图,授予n相对较小:

  • 计算n的{​​{3}}。
  • 选出最大的三个,并将其分配给f1f2f3。如果少于三个因素,请指定1
  • 以递减顺序循环剩余因子 ,将它们乘以当前最小的分区。

修改

我们来看n=60

其主要因素是5 3 2 2

设置f1=5f2=3f3=2

剩余的2乘以f3,因为它是最小的。

我们最终得到5 * 4 * 3 = 60


修改

此算法无法找到最佳通知prime factors注释:

  

考虑17550 = 2 * 3 * 3 * 3 * 5 * 5   * 13.当最佳值为25,26,27时,您的算法将给出15,30,39。


修改

好的,这是我的第二个算法草图,启发式稍微好一些:

  • 将列表L设置为n的{​​{3}}。
  • r设置为n的{​​{3}}。
  • 创建一组三个因素F,最初设置为1
  • 按降序排列素因子:
    • 尝试将当前因子L[i]与每个因子相乘,然后按降序排列。
      • 如果结果小于r,请执行乘法并继续前进到下一个 主要因素。
      • 如果没有,请尝试下一个F。如果超出F s,则乘以最小值。

这适用于17550

的情况
n=17550
L=13,5,5,3,3,3,2
r=25.98

F = { 1, 1, 1 }

迭代1:

  • F[0] * 13小于r,将F设为{13,1,1}

迭代2:

  • F[0] * 5 = 65r更受欢迎。
  • F[1] * 5 = 5小于r,将F设为{13,5,1}

迭代3:

  • F[0] * 5 = 65r更受欢迎。
  • F[1] * 5 = 25小于r,将F设为{13,25,1}

迭代4:

  • F[0] * 3 = 39r更受欢迎。
  • F[1] * 3 = 75r更受欢迎。
  • F[2] * 3 = 3小于r,将F设为{13,25,3}

迭代5:

  • F[0] * 3 = 39r更受欢迎。
  • F[1] * 3 = 75r更受欢迎。
  • F[2] * 3 = 9小于r,将F设为{13,25,9}

迭代6:

  • F[0] * 3 = 39r更受欢迎。
  • F[1] * 3 = 75r更受欢迎。
  • F[2] * 3 = 27大于r,但它是我们可以获得的最小F.将F设为{13,25,27}

迭代7:

  • F[0] * 2 = 26r更受欢迎,但它是我们能得到的最小的F.将F设为{26,25,27}

答案 1 :(得分:3)

这是一种纯粹基于数学的方法,它返回最佳解决方案,不涉及任何类型的排序。天啊,它甚至不需要素数因素。

背景:

1)回想一下多项式

enter image description here

根的总和和乘积由

给出

enter image description here

其中x_i是根。

2)回顾优化理论的另一个基本结果:

enter image description here

,即给定两个变量使得它们的乘积是常数,当两个变量彼此相等时,总和是最小的。波浪变量表示最佳值。

这样做的必然结果是,如果产品是常数的两个变量的总和是最小的,那么这两个变量就彼此相等。

重新制定原始问题:

现在可以将上述问题重新表述为多项式根寻找练习。我们将构造一个满足条件的多项式,并且该多项式的根将是你的答案。如果您需要k个最佳数字,则您将获得度k的多项式。在这种情况下,我们可以用三次方程来讨论

enter image description here

我们知道:

  1. c是输入数字的负数(假设为正数)
  2. a是一个整数且为负(因为因子都是正数)
  3. b是一个整数(它是一次取两个根的总和)并且是正数。
  4. p的根源必须是真实的(并且是积极的,但这已经得到了解决)。
  5. 要解决这个问题,我们只需要根据上述条件最大化a。目前尚未明确知道的唯一部分是条件4,我们可以使用discriminant of the polynomial轻松强制执行。

    对于三次多项式p,判别式为

    enter image description here

    p如果∆>0具有真实且不同的根,并且∆=0具有真实和重合(两个或全部三个)。因此,约束4现在读取∆>=0。现在这很简单,也很容易编程。

    Mathematica中的解决方案

    这是Mathematica中实现此目的的解决方案。

    这是对其他答案/评论中使用的一些数字的测试。

    enter image description here

    左侧的列是列表,右侧列中的相应行给出了最佳解决方案。

    注意:

      

    我只是注意到OP从来没有提到3个数字需要是整数,尽管每个人(包括我自己直到现在)都认为他们是(可能是因为他的第一个例子)。重新阅读问题,并通过立方体示例,似乎OP不是整数。

         

    这是一个重要的观点,它将决定追求和需要定义哪类算法。如果它们不必是整数,则可以提供几种基于多项式的解,其中一种是我的(在放宽整数约束之后)。如果它们应该是整数,那么使用分支-n-bound / branch-n-cut /切割平面的方法可能更合适。

    以下是假设OP意味着三个数字是整数。

    我现在实现它的方式,在某些情况下它可以提供非整数解决方案。

    enter image description here

    这为x提供非整数解决方案的原因是因为我只实现了最大化a,实际上,b也需要最小化(不仅如此,还因为我没有对x_i整数设置约束。可以使用整数根定理,这将涉及找到素数因子,但会使事情变得更复杂。)< / p>

    文本中的Mathematica代码

    Clear[poly, disc, f]
    poly = x^3 + a x^2 + b x + c;
    disc = Discriminant[poly, x];
    f[n_Integer] := 
     Module[{p, \[CapitalDelta] = disc /. c -> -n}, 
      p = poly /. 
         Maximize[{a, \[CapitalDelta] >= 0, 
            b > 0 && a < 0 && {a, b} \[Element] Integers}, {a, b}][[
          2]] /. c -> -n;
      Solve[p == 0]
      ]
    

答案 2 :(得分:2)

正如Anders Lindahl所追求的,可能有一种聪明的方法可以找到最紧密的三重奏,但我会专注于一种更基本的方法。

如果我生成所有三元组,那么我可以随后过滤它们,但我会从那里开始。我知道生成这些的最好方法是使用递归:


f[n_, 1] := {{n}}

f[n_, k_] := Join @@
    Table[
      {q, ##} & @@@ Select[f[n/q, k - 1], #[[1]] >= q &],
      {q, #[[2 ;; ⌈ Length@#/k ⌉ ]] & @ Divisors @ n}
    ]

此函数f采用两个整数参数,即n因子的数量,以及生成k的因子数。

#[[2 ;; ⌈ Length@#/k ⌉ ]] & @ Divisors @ n部分使用Divisors生成n(包括1)的所有除数的列表,然后从第二个中取出这些除数(删除1)除数除以k的天花板。

例如,对于{n = 240, k = 3},输出为{2, 3, 4, 5, 6, 8}

Table命令在累积结果时迭代此列表,将每个元素分配给q

Table的正文为Select[f[n/q, k - 1], #[[1]] >= q &]。这会递归调用f,然后从结果中选择以数字>= q开头的所有列表。

{q, ##} & @@@(也在正文中)然后将q“预先添加”到每个选定的列表中。

最后,Join @@合并了每个Table循环生成的所选列表的列表。


结果是nk部分的所有整数因子,按字典顺序排列。例如:

In[]:= f[240, 3]

Out[]= {{2, 2, 60}, {2, 3, 40}, {2, 4, 30}, {2, 5, 24}, {2, 6, 20},
        {2, 8, 15}, {2, 10, 12}, {3, 4, 20}, {3, 5, 16}, {3, 8, 10},
        {4, 4, 15}, {4, 5, 12}, {4, 6, 10}, {5, 6, 8}}

使用上面给出的函数/算法的输出,然后可以测试三元组的质量,无论如何。

请注意,由于排序,输出中的最后一个三元组是具有最小因子的最大三元组。这通常是结果中最“立方”的,但偶尔也不是。

如果必须找到真正的最佳值,那么从列表的右侧开始测试是有意义的,如果没有快速找到更好的结果则放弃搜索,因为当您向左移动时结果的质量会降低。 / p>


显然这个方法依赖于快速Divisors函数,但我认为这是一个标准的库函数,或者你可以在StackOverflow上找到一个很好的实现。有了这个,这应该是非常快的。上面的代码在我的机器上找到了1.2秒内n为1到10,000的所有三元组。

答案 3 :(得分:1)

修改 以下是使用更高效代码的简短解释,KSetPartitions大大简化了事情。那么Mr.W.的一些建议也是如此。整体逻辑保持不变。

假设 n 至少有3个素因子,

  1. 查找 n 的素因子的三元组KSetPartitions列表。
  2. 将每个子集中的每个元素(素数因子)相乘以产生 n 的三个除数的所有可能组合(当它们相乘时,它们产生 n )。您可以将除数视为正交平行六面体的长度,宽度和高度。
  3. 最接近立方体的平行六面体将具有最短的空间对角线。对每种情况求和三个除数的平方并选择最小的除数。

  4. 以下是Mathematica中的代码:

    Needs["Combinatorica`"]
    g[n_] := Module[{factors = Join @@ ConstantArray @@@ FactorInteger[n]},
      Sort[Union[Sort /@ Apply[Times, Union[Sort /@ 
          KSetPartitions[factors, 3]], {2}]] 
          /. {a_Integer, b_Integer, c_Integer} :>  
               {Total[Power[{a, b, c}, 2]], {a, b, c}}][[1, 2]]]
    

    它可以处理相当大的数字,但随着n的因子数量的增加而显着减慢。以下示例显示240,2400,...... 24000000的时间。 通过考虑一个主要因子在除数中出现不止一次的情况,原则上可以加快这一点。我还没有专业知识。

    In[28]:= g[240]
    
    Out[28]= {5, 6, 8}
    
    In[27]:= t = Table[Timing[g[24*10^n]][[1]], {n, 6}]
    
    Out[27]= {0.001868, 0.012734, 0.102968, 1.02469, 10.4816, 105.444}
    

答案 4 :(得分:1)

不应重新发明轮子,而应将其视为众所周知的 NP-complete 问题的变体。

  • 计算n
  • 的主要因素
  • 计算这些因素的对数
  • 问题转化为将这些日志划分为三个尽可能接近的总和。
  • 此问题被称为 Bin Packing 问题的变体,称为 Multiprocessor scheduling

鉴于多处理器调度问题是NP完全的,难怪找到一个不搜索整个问题空间并找到最佳解决方案的算法很难。

但我想已经有几种算法可以处理Bin-Packing或Multiprocessor-Scheduling,并以有效的方式找到接近最优的解决方案。

另一个相关问题(概括)是 Job shop scheduling 。请参阅维基百科描述,其中包含许多已知算法的链接。


维基百科描述为 (经常使用的LPT算法(最长处理时间) )正是Anders Lindahl首先提出的。