【问题标题】:Most efficient algorithm to compute a common numerator of a sum of fractions计算分数和的公共分子的最有效算法
【发布时间】:2011-07-27 07:41:40
【问题描述】:

我很确定这是解决这个问题的正确网站,但如果它更适合那里,请随意将其移至其他一些 stackexchange 网站。

假设您有一个分数之和a1/d1 + a2/d2 + … + an/dn。您想计算一个共同的分子和分母,即将其重写为p/q。我们有公式

p = a1*d2*…*dn + d1*a2*d3*…*dn + … + d1*d2*…d(n-1)*an
q = d1*d2*…*dn.

计算这些东西最有效的方法是什么,尤其是p?你可以看到,如果你天真地计算它,即使用我上面给出的公式,你会计算出很多冗余的东西。例如,您将计算 d1*d2 n-1 次。

我的第一个想法是迭代计算d1*d2d1*d2*d3、...和dn*d(n-1)dn*d(n-1)*d(n-2),...但即使这样也是低效的,因为您最终会在“中间”计算两次乘法(例如, 如果n 足够大,您将计算d3*d4 两次)。

我确信这个问题可以用一些图论或组合学以某种方式表达,但我还没有研究足够多的东西来对它有一个很好的感觉。

还有一点:我不关心取消,只是最有效的乘法方式。

更新:

我应该知道 stackoverflow 上的人会假设这些是数字,但我已经习惯了我的用例,所以我忘了提及这一点。

我们不能仅仅从每个术语中“划分”出an。这里的用例是一个符号系统。实际上,我正在尝试在 SymPy computer algebra system 中修复一个名为 .as_numer_denom() 的函数,该函数目前以幼稚的方式计算。请参阅corresponding SymPy issue

划分事物有一些问题,我想避免这些问题。首先,不能保证事情会取消。这是因为从数学上讲,(a*b)**n != a**n*b**n 通常是(如果ab 是正数,则它成立,但例如,如果a == b ==-1n == 1/2,你会得到(a*b)**n == 1**(1/2) == 1(-1)**(1/2)*(-1)**(1/2) == I*I == -1)。所以我不认为假设除以an 会在表达式中取消它是一个好主意(这实际上可能是没有根据的,我需要检查代码的作用)。

其次,我还想应用这个算法来计算有理函数的总和。在这种情况下,这些项将自动相乘成一个多项式,并且“除”每个an 将涉及应用多项式除法算法。您可以看到,在这种情况下,您确实希望首先计算最有效的乘法。

更新 2:

我认为我对取消象征性术语的担忧可能是没有根据的。 SymPy 不会自动取消像 x**n*x**(m - n) 这样的东西,但我认为任何可以通过乘法组合的指数也可以通过除法组合,所以应该取消幂。

常量自动分布在添加中存在问题,例如:

In [13]: 2*(x + y)*z*(S(1)/2)
Out[13]: 
z⋅(2⋅x + 2⋅y)
─────────────
      2      

但这首先是bug,其次永远不会是问题(我认为),因为1/2 将通过获取每个术语的分子和分母的算法分成12

尽管如此,我仍然想知道如何在不从每个术语中“划分”di 的情况下做到这一点,以便我可以有一个有效的算法来求和有理函数。

【问题讨论】:

  • a*b 在各种数字系统中与b*a 不同。但是你必须明确地说在这样的系统中工作,否则每个人都认为你在谈论整数。对于(-1)**(1/2)*(-1)**(1/2) == I*I == -1 部分,您必须先阅读一些有关代数和多值“函数”的内容,然后再开始说这些内容。
  • 这与交换性无关。但你确实无意中提出了一个好观点。该算法必须与非交换符号一起使用(在这种情况下,只是分开肯定行不通)
  • 实际上,我认为这对非交换式甚至没有意义。
  • 当你说事物不取消时,你的意思是象征性地,a1*d2*d3*...*dn 不等于 (a1/d1)*d1*d2*d3 *...*dn?如果是这种情况,那么您需要非常明确地描述符号系统的所有规则。你至少有联想吗?而且,如前所述,交换性呢?在这种情况下,“分裂”究竟代表什么?我有点不清楚在这种除法的情况下,您的取幂示例与“取消”有什么关系。
  • 另外,这至少在一个非交换上下文中是有意义的。让 a_i 和 d_i 成为矩阵,其中 a_i/d_i 被理解为 a_i 乘以 d_i 的倒数(当然,我们需要 d_i 是可逆的)。然后,一切仍然有意义。这种情况强调需要明确哪些代数规则是有效的,因为这种情况使 templatetypedef 的答案不适用,但 starblue 的答案仍然可以。

标签: algorithm math


【解决方案1】:

我不会一次性将n 商加起来,而是使用商的成对加法。

  • 如果事物在部分和中被抵消,则数字或多项式会保持较小,这会加快计算速度。

  • 您避免了多次计算同一个产品的问题。

您可以尝试以某种方式对加法进行排序,以增加取消的可能性(也许先添加小分母的商?),但我不知道这是否值得。

如果你从头开始,这实现起来会更简单,但我不确定它是否适合替代 SymPy 中有问题的例程。

编辑:为了更明确,我建议将a1/d1 + a2/d2 + … + an/dn 计算为(…(a1/d1 + a2/d2) + … ) + an/dn

【讨论】:

  • 也许我误解了你的答案,但我不认为加法是这里真正的问题,而是乘法,即重复乘法。最后的添加是由Add(*numers) 完成的,这在 SymPy 中非常有效。对于多项式,你也可以做一些其他的事情(我真的没想那么多)。
  • 重点是每次相加两个商时都要分段进行乘法运算。
  • 对不起。可能是因为这里很晚了(我应该睡一会),但我不明白你在说什么。能举个小例子吗?
  • 我想我明白你现在在说什么了。我需要在纸上解决这个问题,看看它是否真的有效。
  • 我会等到我睡了(可能是个好主意),但我想你可能已经找到了答案。很新奇。我从来没有想过它。
【解决方案2】:

计算两个新数组:

第一个包含左侧的部分倍数:l[0] = 1, l[i] = l[i-1] * d[i]

第二个包含右侧的部分倍数:r[n-1] = 1, r[i] = d[i] * r[i+1]

在这两种情况下,1 是您正在使用的任何环的乘法标识。

然后你的每一个词在顶部,t[i] = l[i-1] * a[i] * r[i+1]

这假设乘法是结合的,但它不需要是可交换的。

作为第一个优化,您实际上不必将r 创建为一个数组:您可以执行第一次通过计算所有l 值,并在一秒钟内累积r 值(向后) 通过计算和。无需实际存储 r 值,因为您按顺序使用每个值一次。

在您的问题中,您说这会计算 d3*d4 两次,但事实并非如此。它确实将两个不同的值乘以d4(一个是右乘,另一个是左乘),但这并不完全是重复操作。无论如何,乘法的总数约为4*n,而2*n 乘法和n 除法在非交换乘法或非域环中不起作用。

【讨论】:

    【解决方案3】:

    如果你想在上面的表达式中计算p,一种方法是将所有分母相乘(在 O(n) 中,其中 n 是分数的数量),让这个值D。然后,遍历所有分数,对于每个分数,分子为 ai,分母为 di,计算 ai * D / d我。最后一项等于分数的分子和除它自己以外的所有分母的乘积。这些项中的每一项都可以在 O(1) 时间内计算出来(假设您使用的是硬件乘法,否则可能需要更长的时间),并且您可以在 O(n) 时间内将它们全部相加。

    这给出了一个 O(n) 时间的算法来计算新分数的分子和分母。

    【讨论】:

    • 似乎太明显了。从来没有真正对基准测试感兴趣,但是除法不是比乘法贵很多吗?我认为他特别要求乘法策略可能是有原因的。
    • @bdares- 我不确定这段代码的绝对性能,但根据 OP 的问题,他似乎没有考虑过(他似乎试图从双方,效率要低得多)。在不知道乘法与除法的相对权衡的情况下,很难设计出运行速度更快的算法。你知道这个数字吗?
    • 这可能适用于较小的n 值。如果 n 足够大,比如 1M+,并且所有分数都有小分母,它会增长到一个巨大的(但不需要)D
    • @templatetypedef 不,完全没有
    • 好的,我忘了排除这个选项。我将在 OP 中添加一些内容来解释原因。
    【解决方案4】:

    还有人向我指出,您可以手动筛选出公分母,然后将它们简单地组合起来而无需乘法。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-08-15
      • 2014-04-12
      • 1970-01-01
      • 2012-12-25
      • 2011-04-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多