快速算法优化算术表达式的序列

10

编辑:澄清问题描述

是否存在解决以下问题的快速算法? 而对于将自然数替换为Z/(2^n Z)的扩展版本,是否也是如此?(在我看来,这个问题过于复杂,无法在同一个地方添加更多的问题。)

问题:

对于给定的自然数集合,例如{7,20,17,100},需要使用一种算法返回计算所有给定数字所需的最短加法、乘法和幂的序列。序列中的每个项目是与以下模式匹配的(正确的)等式:

<number> = <number> <op> <number>

其中<number>是自然数,<op>是{+, *, ^}中的一种。

在这个序列中,<op>的每个操作数都应该是以下之一:

  • 1
  • 已经出现在等式左侧的数字。

示例:

Input: {7, 20, 17, 100}
Output:
2 = 1 + 1
3 = 1 + 2
6 = 2 * 3
7 = 1 + 6
10 = 3 + 7
17 = 7 + 10
20 = 2 * 10
100 = 10 ^ 2

我用 Haskell 写了一个回溯算法。对于像上面那样的小输入它能够正常工作,但我的实际问题是在 [0,255] 范围内随机分布的 ~30 个数字。对于真实的问题,在我的电脑上以下代码需要 2 到 10 分钟。

(实际代码, 非常简单的测试)

我的当前(伪)代码:

-- generate set of sets required to compute n.
-- operater (+) on set is set union.
requiredNumbers 0 = { {} }
requiredNumbers 1 = { {} }
requiredNumbers n =
      { {j, k} | j^k == n, j >= 2, k >= 2 }
    + { {j, k} | j*k == n, j >= 2, k >= 2 }
    + { {j, k} | j+k == n, j >= 1, k >= 1 }

-- remember the smallest set of "computed" number
bestSet := {i | 1 <= i <= largeNumber}

-- backtracking algorithm
-- from: input
-- to:   accumulator of "already computed" number
closure from to =
    if (from is empty)
        if (|bestSet| > |to|)
            bestSet := to
            return
    else if (|from| + |to| >= |bestSet|)
        -- cut branch
        return
    else
        m := min(from)
        from' := deleteMin(from)
        foreach (req in (requiredNumbers m))
            closure (from' + (req - to)) (to + {m}) 

-- recoverEquation is a function converts set of number to set of equation.
-- it can be done easily.
output = recoverEquation (closure input {})

附加说明:

像这样的答案:

  • 没有快速的算法,因为...
  • 有一种启发式算法,它是...

也是可以接受的。现在我感觉没有快速准确的算法...

我认为答案#1可以用作一种启发式方法。


1
http://programmers.stackexchange.com/ 是一个更好的提问平台。也许你可以利用所有操作都是递增的事实,以及部分计算出的数字序列也是递增的这一点。 - Basile Starynkevitch
@viercc 我不同意,Stack Overflow是这方面的好地方。 - jamylak
1
为什么你的输出也解决了不属于你输入的数字?你能详细说明需要做什么吗? - IVlad
@IVlad 我编辑了这个问题。在输出中,每个运算符使用的数字应该是1或已经计算出来的数字。 - viercc
2
@GaborSch,我同意,我的观点是“不”,我认为没有快速算法。无论您是从“1”向前还是从输入向后,搜索空间都会遭受组合爆炸的影响。似乎没有任何办法可以明智地缩小搜索空间,例如,您如何为A*搜索算法提出一些明智的“距离”度量标准。然而,尝试先解决一个更简单的问题,例如仅加法或仅乘法,看看是否会出现任何问题,这将是有趣的。 - TooTone
显示剩余4条评论
2个回答

2

如果你从已排序的输入中最高数字开始向后工作,检查如何利用更小的数字(和正在引入的数字)来构建它,会怎样呢?

例如,虽然这可能不能保证最短的序列...

input: {7, 20, 17, 100}

(100) = (20) * 5 => 
(7) = 5 + 2      => 
(17) = 10 + (7)  =>
(20) = 10 * 2    =>
10 = 5 * 2       =>
5 = 3 + 2        =>
3 = 2 + 1        =>
2 = 1 + 1

让我确认一下您的想法是“当我从众多计算期望数字(例如100)的方法中选择一个时,我不需要选择不使用某个方法(例如我可以忽略100 = 85 + 15的情况)”?如果这是获得最短输出的有效方法,会加快速度。但我无法证明它的有效性。 - viercc
@viercc 是的,我不确定是否可以安排以保证最短序列...这只是一个需要考虑、测试和可能利用的想法/方向。 - גלעד ברקן

2
我建议将其转化为某种图形最短路径算法。
对于每个数字,您计算(并存储)操作的最短路径。技术上说,一步就足够了:对于每个数字,您可以存储操作和两个操作数(左和右,因为幂运算不是可交换的),以及权重(“节点”)。
最初,您要用零的权重注册1。
每次注册一个新数字时,您必须使用所有已注册数字生成该数字的所有计算(所有加法、乘法和幂)。(“边缘”)
过滤计算:如果计算结果已经注册,则不应存储该计算,因为有更简单的方法来获得该数字。
对于可交换的操作,仅存储1个操作。
预先过滤幂运算,因为这可能会导致溢出。
您必须按最短路径(边的权重)对此列表进行排序。权重=(操作数1的权重)+(操作数2的权重)+(1,即操作的权重)
您可以排除所有大于我们必须找到的最大数字的结果(例如,如果我们已经找到了100,则可以排除大于20的任何内容)-这可以细化,以便您还可以检查操作成员。
如果您遇到其中一个目标数字,则找到了计算其中一个目标数字的最短路径,您必须重新开始生成:
重新计算目标数字的最大值。
回到当前找到的数字的路径,将它们的权重设置为0(从现在开始,它们将被给出,因为它们的成本已经支付)。
重新计算代数列表中操作的权重,因为源操作数的权重可能已更改(这导致最后重新排序)-在这里,您可以排除那些其中任何操作数大于新最大值的操作数。
如果所有数字都被命中,则搜索结束。
您可以使用每个目标数字的“反向链接”(操作、左和右操作数)构建表达式。
主要问题是我们始终关注目标函数,即必须使操作总数尽可能少。为了实现这一点,我们始终计算到某个数字的最短路径,然后将该数字(以及沿途的所有其他数字)视为给定数字,然后将搜索扩展到剩余的目标。
从理论上讲,此算法仅处理(注册)每个数字一次。应用适当的过滤器可以削减不必要的分支,因此不会计算两次任何内容(除了队列中元素的权重)。

@viercc 我发现了一个缺陷,即:如果您在表达式中两次使用 2=1+1,则不应将其权重计算两次。因此,我期望的是 [3]4=1+3 [3]5=2+3 [3]6=3+3 而不是 [3]4=1+3 [4]5=2+3 [5]6=3+3。我需要重新考虑如何计算权重,但需要一些时间。 - gaborsch
感谢您的回答和检查。我对您的回答有所考虑:如果有一种加权方法,它应该能够区分input=20=(1+4)*4input=15=(2+3)*3这两种情况。5=1+45=2+3的权重必须根据输入而改变。如果输入是{20},则5=1+4的权重应该比5=2+3小,依此类推。我认为21=7*3=(4+3)*328=(3+4)*435=(2+5)*5之间存在类似的关系,还有许多其他例子,尽管我还没有确认。 - viercc
@viercc 我认为数字5有一个最小权重和一组用于达到该目标的数字集合。如果我的假设是正确的,那么我们需要优化用于实现目标的聚合数字集合。我在考虑重新思考这个问题。在你的例子中,5=[3]{1,2,4}(从 5=1+4=1+22=1+(1+1)(1+1) 计算出来)或者 5=[]{1,2,3}(从 5=2+3=(1+1)+(1+2)=(1+1)+(1+(1+1)) 计算出来)-- 不明显你会选择哪一个。对于15=[4]{1,2,3,5}更好(因为15=35),对于20=[4]{1,2,4,5}更好(因为20=45)。 - gaborsch
@viercc 所以,每个数字都有几种选择,它们甚至可以组合在一起。一切都取决于最终的目标集。我猜测第一步是生成所有可能的选项(显然有些选项可以削减,例如5=(((1+1)+1)+1)+1)生成5=[4]{1,2,3,4}比其他两种组合更差)。第二步是调查结果的AND/OR图,找到所需的最小数字集。 - gaborsch
@viercc 实际上,您可以检查目标数字可能的集合的笛卡尔乘积(例如,'5'有两个集合,{1,2,3}和{1,2,5}),'6'有一个集合{1,2,3},8有两个集合{1,2,4}和{1,2,3} - 因此您有2x1x2个选择。找到最小子集,您就得到了解决方案。 - gaborsch
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接