zoukankan      html  css  js  c++  java
  • 四则运算(挑战出题)解答之轮子哥版-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替换成复杂的子表达式即可,需要注意的是,我们每一次生成+*都要进行上述操作,所以在假设已经产生的表达式时,要符合上述规则调整后的情况。

  • 相关阅读:
    vcs 下使用system verilog调用c函数
    modelsim+win环境下systemverilog调用c函数
    DB2锁与隔离级别
    常用JVM命令
    [转]jvm调优-命令大全(jps jstat jmap jhat jstack jinfo)
    IntelliJ Idea 常用快捷键列表
    Pod配置PersistentVolumeClaim详解
    推荐设计模式好文
    [转载]分布式系统架构经典资料
    阿里云ECS配置iptables
  • 原文地址:https://www.cnblogs.com/vertextao/p/7648844.html
Copyright © 2011-2022 走看看