【问题标题】:Understanding this polynomial division algorithm in a functional language用函数式语言理解这个多项式除法算法
【发布时间】:2018-06-10 20:43:50
【问题描述】:

**这是一种用相当古老的语言标准 ML 实现的算法。我很难理解这个算法:

fun polyquotremd ts ((n,b) :: us) = 
    let fun quo [] qs = (rev qs, [])
        | quo ((m,a) :: ts) qs = 
            if m < n then (rev qs, (m,a) :: ts)
            else
                quo (polysum ts
                    (map (termproduct(m-n, ~a/b)) us))
                    ((m-n, a/b) :: qs)
    in
        quo ts []
    end;

这是目标。我们通过使用元组列表密集地表示多项式,每个元组包含索引 0 处的幂和索引 1 处的系数。例如,$x^2 + 1$ 表示为[(2, 1.0), (0, 1.0)]。这是由 (int*real)list 类型给出的,如下面的辅助函数所示。我们想要得到一个输出(quo, rem),每个都是一个元组列表,分别表示商和余数。 ts 是要被除的多项式,而 us 是除数。

这是我想象的算法的工作原理:

  1. 首先,如果ts = [] 意味着我们没有多项式可除,所以我们返回 qs,我们的累积商结果作为答案。由于我们不断将((m-n, a/b) :: qs) 添加到 qs 列表的头部,因此我们需要反转列表以使列表以最大指数开始。

  2. 第一个 if 条件处理除数的指数 n 大于我们正在处理的当前幂 m 的情况。在这种情况下,我们只是不添加元素并返回(rev qs, (m,a) :: ts),即(quo, rem)

  3. else 条件是我混淆的部分。我知道第二个括号((m-n, a/b) :: qs) 是我们要添加的累积结果,但是polysum ts (map (termproduct(m-n, ~a/b)) us)) 实际上在做什么呢?这背后的数学原理是什么?

通常,我们会一次将多项式除数相除,即 $x+1$ 一起。但是这个算法首先使用 $x$,然后是 $1$。这甚至是如何工作的?为什么会有~a/b(供您参考,意思是-a/b)。


辅助函数:

fun take ([], _) = []
| take (x::xs, i) = if i>0
then x :: take(xs, i-1)
else [];

fun drop ([], _) = []
| drop (x::xs, i) = if i>0 then drop(xs,i-1)
else x::xs;

fun termproduct (m,a) (n,b) = (m+n, a*b) : (int*real);

fun polyproduct [] us = []
    | polyproduct [(m,a)] us = map (termproduct(m,a)) us
    | polyproduct ts us =
    let 
        val k = length ts div 2
    in 
        polysum (polyproduct (take(ts,k)) us)
                (polyproduct (drop(ts,k)) us)
    end;

有人可以向我解释第 3 点吗?我对此感到非常困惑,尤其是这个算法的数学。但它有效.. 仅供参考,此代码可以编译,您可以在标准 ML 中对其进行测试。

【问题讨论】:

  • (添加了 Haskell 标签,因为很多聪明人,他们使用非常相似的语言,检查一下 :)
  • 标签不是为了“让更多人看到问题”。他们是为了“这个问题是关于这个标签所代表的实体”。
  • @JoshCaswell 如果我想让“更多”人看到这个问题,我会添加“javascript”标签:) 相反,函数声明看起来类似于 Haskell 代码,我知道许多喜欢数学的程序员都喜欢。
  • @JoshCaswell 作为原则,我也不同意你的观点。理性的人可能对标签是否完全适合有不同的看法。吸引那些可能对密切相关的类别感兴趣的人的注意力似乎完全可以接受。我的意思是 OP 是来回答问题的,对吧?不要为迂腐的编程百科全书项目服务。
  • “古风”?与目前主流的语言相比,它具有未来感。

标签: algorithm math functional-programming sml


【解决方案1】:

我认为您目前的理解大部分是正确的,而且我不确定您在else 分支中到底不理解什么。

基本思想和不变量

为避免混淆名称隐藏,让ts0 成为传递给polyquotremdus0 = ((n,b) :: us)ts 的值。然后对于 quo ts qsus0 在递归的每个步骤中从外部上下文捕获,除了最后一个,即由返回 quo [] qs = (rev qs, [])if m &lt; n then (rev qs, (m,a) :: ts) 分支处理的那些,以下不变量成立

ts0 = us0 * rev qs + ts

换句话说,我们迭代地(通过递归)生成答案中的下一项 (qs) 和新的ts,其代表当前其余除法的较低最高功率。显然,如果我们可以通过这个过程降低ts的最高功率,使其最高功率m小于us0最高功率n,那么我们在qsts中就有了完整的答案。所以现在我们需要做的就是实际降低ts 的最高功率。我们将得到的只是标准的long division 算法。

长除法算法详解

长除法的一步如下:

  1. 对于被除数(当前剩余)((m,a) :: ts) 和除数 ((n,b) :: us) 中的最高项,即对于 (m,a)(n,b),检查 m &lt; n。如果是 - 完成算法。

  2. (m,a) 除以(n,b) 并求商。明明是(m-n, -a/b)

  3. 将整个除数乘以这个商,即(map (termproduct(m-n, a/b)) ((n,b) :: us)))。请注意,这不是您的代码中的行。稍等片刻,我们也会到达那里。

旁注或如何在代码中完成。以防万一这不清楚:map 是一个经典的高阶函数,它接受另一个函数和一个集合,并返回相同大小的新集合,其中每个元素都是将该函数应用于相应的函数的结果源元素。现在(termproduct(m-n, a/b)) 被称为partial application:它接受一个有两个参数termproduct 的函数并产生一个有一个参数的新函数(原始函数中的第二个),第一个参数固定为(m-n, a/b)。所以整个构造如下:将((n,b) :: us)(或代码中的us)的每个术语乘以(m-n, a/b)。显然,这只是将多项式乘以一项的一种方式。

  1. 从当前的余数中减去这个产品。我们可以通过在上一步中额外乘以 -1 来用 sum 代替这个减法。所以现在是
(polysum ((m,a) :: ts)
                (map (termproduct(m-n, ~a/b)) ((n,b) :: us)))

现在我们可以注意到最高项实际上会相互抵消。这并不奇怪,因为我们将乘数计算为它们的商,即恰好它们会相互抵消。所以现在我们可以删除它们并将该行替换为您的代码中的行:

(polysum ts
                (map (termproduct(m-n, ~a/b)) us))
  1. 最后,我们需要将(m-n, a/b) 的商/乘数添加到我们的结果多项式中。唯一的小问题是我们以相反的顺序填写它,即我们首先找到最高项,下一个,依此类推。这在纸上工作正常,但 ML 中的列表以相反的顺序填写。这就是为什么我们的两个“退出”分支都包含rev qs

如果您想要一个示例 - 您可以关注我上面引用的关于 Polynomial long division 的 wiki 文章中的一个。只是为了重新迭代:该算法的每一步都是另一个递归所有quo

希望这会有所帮助。如果还有什么不清楚的地方请告诉我。

更新(回复评论)

我没有完全理解 ~a/b 在数学上如何相互抵消。

首先,您是否尝试关注我上面提到的example in the wiki?如果是这样,您到底有什么不清楚的地方?

回到您的问题,有几种方法可以查看。从纯机械的角度来看,这里的问题是:termproduct (m-n, ~a/b) (n,b) 的结果是什么?显然是(m-n + n, ~a/b*b) = (m, -a)。所以这与ts 中的最高词完全相反。

从更高级别的算法来看,它们将完全抵消,因为我们计算乘数 (m-n, ~a/b) 的唯一目的是抵消它们。您似乎没有得到我在“基本思想和不变性”部分中描述的内容,我不确定我是否可以以不同的方式重述它,但我会尝试。除法可能被视为多次减法,计数器我们做了多少次。如果我们这样看,很明显我们应该尝试减去除数的一些乘数,以抵消除数的最高项。

示例 1:假设我们将 2*x^2 + 3*x + 4 除以 x^2 + x + 1。如果我们想删除2*x^2 项,我们必须将除数乘以2,然后再减去。所以结果是商=2,其余的是2*x^2 + 3*x + 4 - 2*(x^2 + x + 1)=x + 2

示例 2:假设我们将 2*x^2 + 3*x + 4 除以 x^2 + 2*x + 3。请注意,尽管除数与前面的示例不同,但我们仍然需要将其乘以 2 以删除除数中的最高项。这是因为唯一决定这个乘数的是最高项的比率。显而易见的其他术语会影响最终结果(休息),但它们不会影响结果的当前术语。请注意,结果仍然是商 = 2,但其余的是 2*x^2 + 3*x + 4 - 2*(x^2 + 2*x + 3) = -x - 2

示例 3:让我们将 2*x^3+3*x^2 除以 x+1x+2 以查看差异。请注意,要取消x^3,我们的第一个乘数(即商中的最高项)在两种情况下都应该相同2*x^3/x = 2*x^2 所以对于x+1,我们明白了

x+1: 2*x^3+3*x^2 = 2*x^2*(x+1) + (2*x^3+3*x^2) - 2*x^2*(x+1) = 2*x^2*(x+1) + x^2

同样适用于x+2

x+2: 2*x^3+3*x^2 = 2*x^2*(x+2) + (2*x^3+3*x^2) - 2*x^2*(x+2) = 2*x^2*(x+2) - x^2

从现在开始,结果将有所不同,因为我们继续使用不同的中间休止符+x^2-x^2 用于 x+2。这意味着下一个乘数将是 +x^2/x = +x-x^2/x = -x

x+1: 2*x^3+3*x^2 = (2*x^2+x)*(x+1) + x^2 - x*(x+1) = (2*x^2+x)*(x+1) - x
x+2: 2*x^3+3*x^2 = (2*x^2-x)*(x+2) - x^2 - (-x)*(x+2) = (2*x^2-x)*(x+2) + 2*x

现在最后一步是 -x for x+1 => multiplier 是 -12*x for x+2 => multiplier 是 2

x+1: 2*x^3+3*x^2 = (2*x^2+x-1)*(x+1) - x - (-1)(x+1) = (2*x^2+x-1)*(x+1) + 1
x+2: 2*x^3+3*x^2 = (2*x^2-x+2)*(x+2) + 2*x - (2)*(x+2) = (2*x^2-x+2)*(x+2) - 4

您可以再次看到除数的非最高项的变化会影响结果,但不会影响结果的最高项。

【讨论】:

  • 感谢您的回复!但是,我不完全了解~a/b 在数学上如何相互抵消。我一直认为在长除法中,我们使用整个除数来划分方程,例如,$x^2+1$,但算法先使用 $x^2$,然后使用 $1$。从数学上讲,这是如何工作的?我想为了完全理解算法,我错过了数学概念。
  • @oldselflearner1959,我试图在我的答案更新中回答您的评论。如果仍然不清楚,您可能应该更详细地澄清究竟是什么不清楚。
猜你喜欢
  • 1970-01-01
  • 2015-10-28
  • 2012-06-15
  • 1970-01-01
  • 2017-11-19
  • 2018-10-15
  • 2010-09-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多