OO第一单元博客递归下降

阿得旺斯 学生 2024-03-21 14:35:27

总述

第一单元三次作业都是对一行表达式进行去括号整理 。本人借鉴了课程组在公众号发布的代码,使用递归下降过程先将表达式解析为因子,再将因子链表解析为语法树,最后将语法树解析为由单项式组成的多项式。

第一次作业总架构

Main.class的PlantUML parser示意图

img

预处理prePro

预处理可以是解决表达式处理尤其是符号处理的最佳方法。时间和空间复杂度低、操作简单,所以如果有些方法可以写入预处理类,一定要优先在预处理运行,写入递归下降不仅复杂度高,而且很难debug,往往事倍功半。
预处理首先将输入的表达式字符串所有的空字符去除(StringBuilder)然后对特殊的符号进行处理,比如replace("+-","-"),replace("(+","(0+")等等。值得注意的是,由于括号嵌套和符号位,可能会出现“---”和“+++”的情况,也要在预处理里面解决。

表达式解析

把预处理后的字符串加入解析,本人几乎沿用了公众号的代码架构。只是为了处理^因子,以及()^类型的数据,本人引入了核心因子: CoreFactor,即Expr -> Term -> CoreFactor -> Factor四级架构。

    public CoreFactor parserCore() {
        ArrayList<Factor> factors = new ArrayList<>();
        factors.add(parserFactor());
        while (lexer.notEnd() && lexer.now().getType() == Token.Type.EXP) {
            lexer.move();
            factors.add(parserFactor());
        }
        return new CoreFactor(factors);
    }

如果第四级提取到了(,则进行递归。叶节点的类型有Num,Letter(字母),第二次作业又引入了ExPexP来表示exp()^()因子。经过解析,一行字符串成功转化成了一个四级树结构。

四级树递归结构

img

从树结构到单项式结构

单项式Mono:系数C,指数E,结构 C*x^E
多项式Poly:由Mono组成的链表
注意树的每一级都有toPoly()结构。底层Factor都是直接生成Mono随后,然后再CoreFactor的toPoly里形成Poly,并在Term和Expr级进行组合。

符号处理

本人在递归构建Poly时,为了处理符号,在Mono中设立了符号位,并在Expr一级把每一个Term所有mono的符号和ops的符号进行check

Mono的合并

如果不进行Mono合并的话,((1+x)^5)^5就能TLE,所以本人在Expr一级实现toPoly并且检测符号位以后,会根据每个Mono的toString()进行合并,如此可以极大减小Poly的数据量,从而避免再Expr返回给Factor——toPoly一个超级亢杂的链表。

处理输出FinPro

对最终返回的一个多项式转化成字符串链表,对系数进行合并相加,对符号进行处理,并且对系数为0,指数为1的情况进行if判断,最终返回要输出的字符串。

第二次作业处理

为了实现作业的可迭代性,本人尽可能对底层的方法进行封装,如果有需要改动的,也尽可能另写一个方法,而不是随便对原来的代码进行修改。

函数的预处理

函数类方法

PrePro还是比较简单的,虽然可能会增加时间复杂度 。本人新建了CustFun函数类,并在Main留下了Hashmap链表。在函数类里,本人记录函数名称,提取了形参个数,并将等号后面的函数表达式里面的形参替换为&,|,%。并构建替换方法,输入的参数为实参,返回的是将表达式的&%|替换为形参后的字符串。

替换注意

exp也含有x,为了防止替换破坏exp,本人先把exp替换为j,并在xyz替换后,把j换成exp。在替换时,替换形参时,本人额外嵌套了()并在最后返回含有实参的函数表达式时又添加了()。

预处理递归

在预处理中函数替换方法,方法的形参为一行字符串返回值为字符串,本人遍历字符串,一旦检测到fgh就提取实参 ,如果没有fgh则返回字符串本身,否则调用函数替换方法把分()替换,最后返回值为,再次调用本方法(参数为本次替换后的字符串)的返回值。
虽然略微增加复杂度,但是可以保证替换的准确性。

exp结构处理

第一次作业Mono为Cx^E
本次则是C
x^E*exp(Poly)
对exp()^n,则直接更改exp()内的String,更改后再处理。
所以Mono中要存系数C,指数E,符号位sign,还有exp()内的exPoly。

示意图

img

递归区别

原本只是自下而上把所有Mono存入上层的Poly,而现在,每个Mono中可能有新的Poly,好像一棵树上的一个树枝。
除此之外在合并时,含有exp的Mono和不含有的要独立判断,分开合并。

第三次作业

输入函数非递归调用

无需更改

求导处理

参考第二次实验求导方法,根据链式法则和乘法法则进行自上而下的求导。

尤其注意

因为Mono中可能存在Poly,而Mono存取的只是Poly的地址(指针),所以克隆Mono时如果只是浅克隆,会导致两个Mono调用同一个expoly,产生Wrong Answer。
因此一定要在Mono和Poly中写递归克隆方法。

bug分析与程序优化

复杂度分析

预处理复杂度

img

递归复杂度

img

架构复杂度主要集中在这两处。如果要优化,只能更改底层逻辑。

底层封装与重构

底层的封装是最重要的,为了代码的可延展性,底层一般只提供普适性的方法。如果有新的需求,那么就增加新的方法,而不是更改底层代码。假如一定要极大改动,那么说明底层结构不具备可迭代性,最好是重构。

底层类不返回new此类对象

本人Mono类里面的方法没有方法返回Mono对象,别的底层类亦然。

克隆方法

因为底层不返回与自己类型相同的对象,因此非常有必要写一个clone()方法。尤其是类似于Mono中存取Poly的指针这样的结构,一定要写好克隆方法。

体会与未来展望

OO真的是一门很难但是很有意思的课,看着自己的代码 一点一点形成一个工程真的很有成就感。同时本人也建议适当降低互测门槛,让更多的同学进入互测,并合理分配房间,让所有人都能体验到hack与被hack。

...全文
88 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

301

社区成员

发帖
与我相关
我的任务
社区描述
2023年北航面向对象设计与构造
学习 高校
社区管理员
  • YannaZhang
  • CajZella
  • C_ecelia
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧