【问题标题】:Transform a parse tree of a polynomial to a parse tree of its evaluation according to Horner's scheme根据霍纳的方案,将多项式的解析树转换为其评估的解析树
【发布时间】:2012-04-07 08:12:22
【问题描述】:

能否请您指出一种算法,该算法采用(二元)解析树来评估单个变量中的多项式表达式,并返回一个等效的解析树,该解析树根据霍纳规则评估多项式。

预期的用例在表达式模板中。这个想法是,对于矩阵x,解析树从

a + bx + c * x*x + d * x*x*x...

会被优化成对应的解析树

a + x *( b + x( c + x*d))

【问题讨论】:

  • monomial basis中的表达式吗?
  • @Shahbaz 不完全是,因为它可用作带有 + 和 * 操作的解析树
  • 算法下线了吗?它应该有多高效?
  • @Shahbaz 它本质上是一个优化过程,将在多项式的多次评估中分摊。所以它不需要非常快。也许方法是从树中得到单项式系数,然后使用标准的霍纳法。
  • 我相信如果你将表达式扩展为单项式,那么应用霍纳定律将是微不足道的。

标签: algorithm expression-trees compiler-optimization parse-tree expression-templates


【解决方案1】:

您可以使用以下转换。

假设:多项式的解析树按照指数递增的顺序——如果这个假设不成立,可以在解析树中交换部分多项式以使假设成立

假设:解析树包含变量的指数形式(例如x^2)而不是乘法形式(例如x*x),x^0 除外——简单的转换可以在两个方向

假设:多项式中的系数(如果为常数)不为零——这是为了避免不得不处理(a+0*x+c*x^2 -> a+x(cx) 而不是a+cx^2

解析a+b*x^1+c*x^2+d*x^3的树:

  .+..
 /    \
a   ...+....
   /        \
  *         .+..
 / \       /    \
b  ^      *      *
  / \    / \    / \
 x   1  c   ^  d   ^
           / \    / \
          x   2  x   3

转换为a+x(b+c*x^1+d*x^2)

  +
 / \
a   *
   / \
  x   +
     / \
    b   .+..
       /    \
      *      *
     / \    / \
    c   ^  d   ^
       / \    / \
      x   1  x   2

转换为a+x(b+x(c+d*x^1))

  +
 / \
a   *
   / \
  x   +
     / \
    b   *
       / \
      x   +
         / \
        c   *
           / \
          d   ^
             / \
            x   1

最后(a+x(b+x(c+d*x))):

  +
 / \
a   *
   / \
  x   +
     / \
    b   *
       / \
      x   +
         / \
        c   *
           / \
          d   x

常见的变换是:

 .            ->    .
  .           ->     .
   .          ->      .
    +         ->      .*..
   / \        ->     /    \
  *   N(k+1)  ->    ^      +
 / \          ->   / \    / \
ck  ^         ->  x   k  ck  N'(k+1)
   / \        -> 
  x   k       -> 

其中N'(k+1) 是与N(k+1) 相同的子树,每个指数j 替换为j-k(如果k 为1,则将x^k 子树替换为x

然后在N'(k+1) 上再次应用(*)该算法,直到其根为*(而不是+),表明达到了最终的偏多项式。如果最终指数为 1,则将指数部分替换为 x (x^1 -> x)

(*) 这里是递归部分

注意:如果您累积跟踪 N(k+1) 子树中的指数变化,您可以将最后两个步骤放在一起,一次递归地转换 N(k+1)

注意:如果你想允许负指数,那么

a) 提取最大的负指数作为第一项:

a*x^-2 + b*x^-1 + c + d*x + e*x^2  ->  x^-2(a+b*x+c*x^2+d*x^3+d*x^4)

并应用上述转换

或 b) 分离方程的正指数部分和负指数部分,并分别对每个部分应用上述变换(您需要“翻转”负指数端的操作数并将乘法替换为除法):

a*x^-2 + b*x^-1 + c + d*x + e*x^2  ->  [a+x^-2 + b*x-1] + [c + d*x + e*x^2] ->
-> [(a/x + b)/x] + [c+x(d+ex)]

这种方法似乎比 a) 更复杂

【讨论】:

  • 你在答案中做了很多工作,因此 tghe upvote 但我真的在看一个由 + 和 * 组成的表达式树
  • @san - 如果您在表达式树中偶尔引用 x^k,您可以轻松地将其转换为最后的 xx...*x。即使在中间步骤中,您也不能处理指数吗?
  • @san - 将上面的每个x^k 替换为对应的x*x*...*x 子树,然后a)计算它们的数量(得到k)或b)一个接一个地删除*x(并应用上述 aglorithm 和 x 删除每个)直到它们全部消失
  • @san,你不是说这棵树不是单项式吗?
  • @Shahbaz 确实不是。我认为该解决方案所说的与您的解决方案相似,即折叠“*”直到您达到单项式,然后进行相当标准的操作。
【解决方案2】:

你只需要应用以下规则,直到你不能再应用它们。

((x*A)*B)     -> (x*(A*B))
((x*A)+(x*B)) -> (x*(A+B)))
(A+(n+B))     -> (n+(A+B))  if n is a number

其中AB 是子树。

这是一个执行此操作的 OCaml 代码:

type poly = X | Num of int | Add of poly * poly | Mul of poly * poly

let rec horner = function
  | X -> X
  | Num n -> Num n
  | Add (X, X) -> Mul (X, Num 2)
  | Mul (X, p)
  | Mul (p, X) -> Mul (X, horner p)
  | Mul (p1, Mul (X, p2))
  | Mul (Mul (X, p1), p2) -> Mul (X, horner (Mul (p1, p2)))
  | Mul (p1, p2) -> Mul (horner p1, horner p2) (* This case should never be used *)
  | Add (Mul (X, p1), Mul (X, p2)) -> Mul (X, horner (Add (p1, p2)))
  | Add (X, Mul (X, p))
  | Add (Mul (X, p), X) -> Mul (X, Add (Num 1, horner p))
  | Add (Num n, p)
  | Add (p, Num n) -> Add (Num n, horner p)
  | Add (p1, Add (Num n, p2))
  | Add (Add (Num n, p1), p2) -> Add (Num n, horner (Add (p1, p2)))
  | Add (p1, p2) -> horner (Add (horner p1, horner p2))

【讨论】:

  • 这真的很优雅和一般。你知道是否在任何地方都证明了这三个转换就足够了。
【解决方案3】:

你可以通过递归函数得到树的monomial coefficients。根据霍纳定律转换这些系数并得到一个表达式将是simple

我可以给你一个简单的递归函数来计算这些值,即使存在一个更有效的可能

理论知识

首先,让我们制定表达式。一个表达式E

E = a0 + a1x + a2x^2 + ... + anx^n

可以写成 (n+1) 元组:

(a0, a1, a2, ..., an)

然后,我们定义两个操作:

  • 加法:给定两个表达式E1 = (a0, ..., an)E2 = (b0, ..., bm)E1 + E2对应的元组为:

              {(a0+b0, a1+b1, ..., am+bm, a(m+1), ..., an) (n > m)
    E1 + E2 = {(a0+b0, a1+b1, ..., an+bn, b(n+1), ..., bm) (n < m)
              {(a0+b0, a1+b1, ..., an+bn)                  (n = m)
    

    也就是说,有max(n,m)+1 元素,而ith 元素由(使用C-ish 语法)计算:

    i<=n?ai:0 + i<=m?bi:0
    
  • 乘法:给定两个表达式E1 = (a0, ..., an)E2 = (b0, ..., bm)E1 * E2对应的元组是:

    E1 * E2 = (a0*b0, a0*b1+a1*b0, a0*b2+a1*b1+a2*b0, ... , an*bm)
    

    即有n+m+1元素,而ith元素由

    计算
    sigma over {ar*bs | 0<=r<=n, 0<=s<=m, r+s=i}
    

因此递归函数定义如下:

tuple get_monomial_coef(node)
    if node == constant
        return (node.value)  // Note that this is a tuple
    if node == variable
        return (0, 1)        // the expression is E = x
    left_expr = get_monomial_coef(node.left)
    right_expr = get_monomial_coef(node.right)
    if node == +
        return add(left_expr, right_expr)
    if node == *
        return mul(left_expr, right_expr)

在哪里

tuple add(tuple one, tuple other)
    n = one.size
    m = other.size
    for i = 0 to max(n, m)
        result[i] = i<=n?one[i]:0 + i<=m?other[i]:0
    return result

tuple mul(tuple one, tuple other)
    n = one.size
    m = other.size
    for i = 0 to n+m
        result[i] = 0
        for j=max(0,i-m) to min(i,n)
            result[i] += one[j]*other[i-j]
    return result

注意:在mul的实现中,j应该从0迭代到i,同时还必须满足以下条件:

j <= n (because of one[j])
i-j <= m (because of other[i-j]) ==> j >= i-m

因此,j 可以从 max(0,i-m)min(i,n) 出发(等于 n,因为 n &lt;= i

C++ 实现

既然您已经有了伪代码,那么实现应该不难了。对于元组类型,一个简单的std::vector 就足够了。因此:

vector<double> add(const vector<double> &one, const vector<double> &other)
{
    size_t n = one.size() - 1;
    size_t m = other.size() - 1;
    vector<double> result((n>m?n:m) + 1);
    for (size_t i = 0, size = result.size(); i < size; ++i)
        result[i] = (i<=n?one[i]:0) + (i<=m?other[i]:0);
    return result;
}

vector<double> mul(const vector<double> &one, const vector<double> &other)
{
    size_t n = one.size() - 1;
    size_t m = other.size() - 1;
    vector<double> result(n + m + 1);
    for (size_t i = 0, size = n + m + 1; i < size; ++i)
    {
        result[i] = 0.0;
        for (size_t j = i>m?i-m:0; j <= n; ++j)
            result[i] += one[j]*other[i-j];
    }
    return result;
}

vector<double> get_monomial_coef(const Node &node)
{
    vector<double> result;
    if (node.type == CONSTANT)
    {
        result.push_back(node.value);
        return result;
    }
    if (node.type == VARIABLE)
    {
        result.push_back(0.0);
        result.push_back(1);  // be careful about floating point errors
                              // you might want to choose a better type than
                              // double for example a class that distinguishes
                              // between constants and variable coefficients
                              // and implement + and * for it
        return result;
    }
    vector<double> left_expr = get_monomial_coef(node.left);
    vector<double> right_expr = get_monomial_coef(node.right);
    if (node.type == PLUS)
        return add(left_expr, right_expr);
    if (node.type == MULTIPLY)
        return mul(left_expr, right_expr);
    // unknown node.type
}

vector<double> get_monomial_coef(const Tree &tree)
{
    return get_monomial_coef(tree.root);
}

注意:此代码未经测试。它可能包含错误或错误检查不足。确保你理解它并自己实现它,而不是复制粘贴。

从这里开始,您只需要根据此函数为您提供的值构建一个表达式树。

【讨论】:

  • 是的,这个想法应该可行,赞成。我暂时推迟接受,希望有一个解决方案可以直接解决它而不形成单项式系数元组。
猜你喜欢
  • 2016-09-20
  • 2017-07-13
  • 2018-03-20
  • 2013-02-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多