四则运算(挑战出题)解答之轮子哥版-2

四则运算(挑战出题)解答之轮子哥版-2

上一次解读 轮子哥版本四则运算(挑战出题)的博客在 这里。但是,上次博客中只是针对交换律做解读,而轮子哥还考虑了结合律,在归一化部分体现。

尽可能左结合

在生成表达式时尽可能左结合。什么意思呢?

比如:

  • 1+2+3 是左结合,其后缀表达式:12+3+
  • 1+(2+3) 不是左结合,其后缀表达式:123++,我们要避免生成这样的表达式

为什么要这么做?

以上两个算式考虑结合律的情况下是相同的算式,但是后缀表达式不同,而我们目的是要简单通过后缀表达式是否相同来判断算式是否相同,所以生成的时候按规则左结合来避免这种情况。

好处?

对于string的比较和去重已经非常成熟,不需要我们重复造轮子,C++中 set<string> 就能搞定。

如果是构造表达式树(树结构)去重,要么需要对树进行翻转递归比较(效率低),要么需要自己对表达式树做可靠的hash(又是造轮子)后比较hash值。

怎么做?

我们是直接生成后缀表达式,从上面可以观察到连续生成+或者*不是我们想要的,所以在生成的过程中需要避免。

根据上一篇博客介绍,我们在生成过程中维护了表达式树的结构,我们在随机生成新的字符时,只需要观察右子树最后一个字符是什么(其实就是整个表达式最后一个字符),如果是+或者*,我们就避免重复生成。

归一化

  • 使得相同表达式的后缀表达式是一样
  • 交换律/结合律同时考虑(只有+*运算符需要
  • 边生成边进行,而不是生成完了再来做

引用一段代码注释:

针对交换律和结合律进行归一化。
也就是说,无论是(1+2)*(3+4)还是(4+3)*(2+1)都将返回12+34+*。
因为"12+"<"34+",所以顺序将会被调整,把小的放在左边。
因此在产生 (4+3)*(2+1) 的过程中
    完成43+就立即调整为34+
    完成21+就立即调整为12+
    完成34+12+*就立即调整为12+34+*

即在生成运算符(+*)的时候来做该运算符所属的两棵子表达式的顺序调整(字符串排序),当前运算符的位置不变。

回到刚才的尽量左结合问题,在上述归一化过程中,可能会产生不是尽可能左结合的情况,比如:在产生+的情况下,当前表达式是23+1(两边的表达式分别是23+1),直接调整后就是123++,不是左结合。

这种情况如何处理呢?

举例:

  • 如果这次生成+,在向前分割表达式树时,如果还遇到运算符是+的表达式树,就继续分解下去,继续描述上述情况,即23+的运算符是+(与本次生成的运算符相同),应该继续被分解成23 两个子表达式,然后对各个子表达式进行排序,运算符位置不变,即得到12+3+

    • 这里的1,2,3可以代表复杂的子表达式。
  • 对于生成*的情况是相同的。

这部分代码位于:https://gist.github.com/vczh/2c058aed996effc0a519ed3d265a3eb5#file-xinz_expr_gen-cpp-L196


看几个实际的例子

简单起始情况

  • 假设已经产生的表达式:32
  • 当前生成 * -> 32*(中缀表达式为 3 * 2
  • 乘法可交换,因此要拆分其左右表达式树,做排序
    • 往前拆分子表达式得到 3, 2
    • 排序 "2" < "3", 得到 23
    • 做拆分的运算符位置都保持不变,得到 23* (中缀表达式为 2 * 3

当前生成 + 时同理,即无论生成:

  • 23+
  • 32+

最后都会是 23+

稍复杂点

  • 假设已经产生的表达式:85-4/712+-
  • 当前生成 * -> 85-4/712+-*(中缀表达式为 (8-5)/4*(7-(1+2))
  • 乘法可交换,因此要拆分其左右表达式树,做排序
    • 往前拆分子表达式得到 85-4/, 712+-
    • 排序 "712+-" < "85-4/", 得到 712+-85-4/
    • 做拆分的运算符位置都保持不变,得到 712+-85-4/*(中缀表达式为 (7-(1+2)) * ((8-5)/4)

交换导致违反尽可能左结合的简单情况

这种情况在上面已经说过,还是简单罗列一下:

  • 假设已经产生的表达式:23+1
  • 当前生成 + -> 23+1+(中缀表达式为 2+3+1
  • 加法可交换,因此要拆分其左右表达式树,做排序
    • 往前拆分子表达式得到 23+, 1
    • 排序 "1" < "23+", 得到 123+
    • 做拆分的运算符位置都保持不变,得到 123++ (中缀表达式为 1+(2+3)

显然这不是我们所希望发生的,因为这将阻碍我们想简单通过后缀表达式来判断重复。怎么处理这种情况,上面也已经说了,这里也简单列出:

  • 加法可交换,因此要拆分其左右表达式树,做排序
    • 往前拆分子表达式得到 23+, 123++ 运算,与当前产生的运算符相同,所以要继续对这个子表达式做拆分,得到以下子表达式:
      • 2,3,1
    • 排序 "1" < "2" < "3", 得到 123
    • 做拆分的运算符位置都保持不变,得到 12+3+ (中缀表达式为 1+2+3

同样,当前生成*时,子表达式为 * 运算时,也要继续拆分。

交换导致违反尽可能左结合的稍复杂情况

其实将上一个例子中的1,2,3替换成复杂的子表达式即可,需要注意的是,我们每一次生成+*都要进行上述操作,所以在假设已经产生的表达式时,要符合上述规则调整后的情况。

原文地址:https://www.cnblogs.com/vertextao/p/7648844.html