我有一个数字n
,我想找到三个数,它们的乘积是n
,但彼此之间尽可能接近。也就是说,如果n = 12
,那么我希望得到3、2、2作为结果,而不是6、1、2。
另一种思考方式是,如果n
是一个长方体的体积,那么我想找到边长,使得长方体尽可能地像一个立方体(即,长度尽可能相似)。这些数字必须是整数。
我知道这个问题不太可能有完美的解决方案,我很乐意使用大多数情况下能给出好答案的方法,但我不知道该如何设计这个算法。有什么建议吗?
我有一个数字n
,我想找到三个数,它们的乘积是n
,但彼此之间尽可能接近。也就是说,如果n = 12
,那么我希望得到3、2、2作为结果,而不是6、1、2。
另一种思考方式是,如果n
是一个长方体的体积,那么我想找到边长,使得长方体尽可能地像一个立方体(即,长度尽可能相似)。这些数字必须是整数。
我知道这个问题不太可能有完美的解决方案,我很乐意使用大多数情况下能给出好答案的方法,但我不知道该如何设计这个算法。有什么建议吗?
这是我的第一个算法草稿,假设n
相对较小:
n
的质因数。f1
,f2
,f3
。如果小于三个因子,则分配1
。编辑
让我们以n=60
为例。
它的质因数是5 3 2 2
。
设置f1=5
,f2=3
和f3=2
。
剩下的2
被乘到f3
中,因为它是最小的。
我们最终得到5 * 4 * 3 = 60
。
编辑
请注意,此算法将不会找到最优解,参见btilly的评论:
考虑17550 = 2 * 3 * 3 * 3 * 5 * 5 * 13。您的算法会给出15、30、39,而最佳答案是25、26、27。
编辑
好的,这是我的第二个算法草稿,带有稍微更好的启发式:
L
设置为n
的质因数。r
设置为n
的立方根。F
,最初设置为1
。L[i]
与每个因子按降序相乘。
r
,则执行乘法并继续下一个质因数。F
。如果超出F
,则乘以最小的。对于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 = 65
大于r
。F[1] * 5
小于r
,则将F
设置为{13,5,1}
。迭代3:
F[0] * 5 = 65
大于r
。F[1] * 5
小于r
,则将F
设置为{13,25,1}
。迭代4:
F[0] * 3 = 39
大于r
。F[1] * 3 = 75
大于r
。F[2] * 3
小于r
,则将F
设置为{13,25,3}
。迭代5:
F[0] * 3 = 39
大于r
。F[1] * 3 = 75
大于r
。F[2] * 3
小于r
,则将F
设置为{13,25,9}
。迭代6:
F[0] * 3 = 39
大于r
。F[1] * 3 = 75
大于r
。F[2] * 3 = 27
大于r
,但它是我们能得到的最小的F
。将F
设置为{13,25,27}
。迭代7:
F[0] * 2 = 26
大于r
,但它是我们能得到的最小的F
。将F
设置为{26,25,27}
。68、318、1098
会得出什么结果?如果我编码正确,我得到的是{1, 4, 17}
、{1, 6, 53}
和{2, 9, 61}
,但我认为{2, 2, 17}
、{2, 3, 53}
和{3, 6, 61}
更好。 - Mr.Wizardx_i
是根。k
个最优数,你将需要一个 k 次多项式。在这种情况下,我们可以用一个三次方程来描述它。
我们知道:c
是输入数的相反数(假设为正数)。
2. a
是负整数(因为因子都是正数)。
3. b
是整数(根据两个根之和),且为正数。
4. 多项式 p
的根必须是实数(并且是正数,但已经解决了这个问题)。a
。现在唯一不明确的部分是第四个条件,我们可以使用多项式的判别式轻松实现它。p
,其判别式为
如果 ∆>0
,则 p
具有实且不同的根,如果 ∆=0
,则 p
具有实且重复的根(其中两个或全部三个)。因此,约束条件 4 现在变成了 ∆>=0
。这现在很简单,易于编程。
这是一个在Mathematica中实现的解决方案。
以下是对其他答案/评论中使用的一些数字进行测试的结果。
左侧列是列表,右侧列中的相应行给出了最佳解决方案。
我刚刚注意到OP从未提到需要三个数字是整数,尽管每个人(包括我自己)都假设它们是整数(可能是因为他的第一个例子)。重新阅读问题,并根据立方体示例,似乎OP并没有固定在整数上。
这是一个重要的问题,将决定要追求哪类算法,并需要定义。如果它们不需要是整数,则可以提供几种基于多项式的解决方案之一,其中之一是我的解决方案(放松整数约束后)。如果它们应该是整数,则可能使用分支和界限/分支和割平面的方法更合适。
以下内容是在假定OP意味着三个数字是整数的情况下编写的。
我目前实现的方式在某些情况下可以给出非整数解。
这会导致x
的非整数解,因为我只最大化了a
,实际上,b
也需要最小化(不仅如此,还因为我没有对x_i
是整数施加约束。可以使用“整数根定理”,它涉及到找到质因数,但会使事情变得更加复杂)。
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]
]
O(1)
。你能否尝试使用 c=2*3*5*7*...*997
或 c=2*3*5*7*...*999983
来测试一下,告诉我们它的执行速度如何? - ypercubeᵀᴹ0<b<c
或-c<a<0
;例如,考虑n=2
。三个最优数是1,1,2
,其中a=-4
,b=5
,c=2
。代码的中心部分就是这样:最大化a
,使得a
为负整数,b
为正整数且判别式∆
为非负数。一旦找到a
和b
,则将其代入原多项式并找到其根。 - user616736可能有一种聪明的方法来找到最紧密的三元组,就像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
。
Select[f[n/q, k - 1], #[[1]] >= q &]
是Table
的主体。它递归调用f
,然后从结果中选择以>= q
开头的所有列表。
{q, ##} & @@@
(也在主体中)然后在这些选定的列表的每个循环中“前置”q
。
最后,Join @@
合并由Table
的每个循环产生的这些选定列表的列表。
结果是将n
的所有整数因子分成k
部分,按字典顺序排列。例如:
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}}
编辑 这里有一个更简洁的解释,使用了更高效的代码,KSetPartitions 大大简化了事情。Mr.W 的一些建议也起到了作用。总体逻辑保持不变。
假设 n 至少有 3 个质因数,
KSetPartitions
列表。以下是在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}
Flatten[FactorInteger[n] /. {a_Integer, b_Integer} :> Table[a, {b}]]
应该被替换为 Join @@ ConstantArray @@@ FactorInteger@n
(2) 我认为 Sqrt
是多余的 (3) 最好一次将所有数字平方,因为 Power
是 Listable
并且使用 Total
相加比使用规则 {a_, b_, c_} :> ...
处理每个三元组更快 (4) SortBy 应该比您正在使用的装饰和排序方法更快 - Mr.WizardSort
。
(6) 生成所有集合分区非常低效。好了,我想这应该足够攻击你的代码了。 :o) - Mr.WizardKSetPartitions
仍然不是最优的,因为它会出现重复(这些重复被 Union
去除)。这并没有更快,但代码更加简洁: g2[n_] := Module[{factors = Join @@ ConstantArray @@@ FactorInteger[n]}, SortBy[ Union[ Sort /@ Apply[Times, Union[Sort /@ KSetPartitions[factors, 3]], {2}]], Total[#^2] & ][[1]] ]
- Mr.Wizard