2019面向对象程序设计——多项式求导之优化篇
作者:17376482 王育斌
优化前言:
笔者励志于介绍相对全面并且难度递进的优化方法。笔者在三次作业的优化中顺利通过了所有强测点,但在互测中依旧暴露了微小的问题。在三次作业中我对ArrayList、HashMap和LinkedList对于元素的存取、管理与删除进行了较多的试验,个人不推荐在多项式求导这一单元有意向进行优化的同学使用HashMap,HashMap虽然对于元素的存取相比List而言有得天独厚的优势,但在管理方面相比List复杂,在本单元的优化中,一部分适用于List的管理方法,HashMap却不适用。当然不想优化的话,力荐HashMap。
一.基础化简之正项前移(适用于第一、二、三次作业)
难度等级:I
第一次作业能够进行的优化不多,主要是“正项前移”的问题,将正项放在前面可以省去一个正号,使得输出长度缩短,当然,正项前移问题在第二、三次作业中也需要考虑。个人推荐正项前移放在全部优化的最后一步。
利用工具类Collections的静态方法sort对List进行排序
Collections.sort(terms, new Comparator<Term>() {
@Override
public int compare(Term o1, Term o2) {
return o2.getFactors().get(0).getCoeff()
.compareTo(o1.getFactors().get(0).getCoeff());
}
});
重写compare方法,按系数由大到小的顺序对项进行排序,可使负项在正项之后。
二.基础化简之去除零项(适用于第一、二、三次作业)
难度等级:I
0*x*sin(x)*cos(x)
这种输出看起来也许让人有些不适。
不妨利用迭代器iterator遍历List,对每一项进行系数判断,若系数为0,则利用remove()方法对这一项进行删除,循环删除与添加会造成异常,后面的高级化简也均需考虑这一问题,如何有效避免,请读者思考。
Iterator<Term> it = terms.iterator();
while (it.hasNext()) {
Term term = it.next();
if (term.getFactors().get(0).getType().equals(FactorType.constant)
&& term.getFactors().get(0).getCoeff().equals(zero)) {
it.remove();
}
}
效果如图,看起来清爽多了叭~
三.进阶化简之利用三角函数公式进行数学化简(适用于第二次作业)
难度等级:II
笔者在第二次作业种主要利用了如下四个化简公式进行化简:
1°
a*sin(x)^(m+2)*cos(x)^m + b*sin(x)^m*cos(x)^(m+2) =
a*sin(x)^m*cos(x)^m+(b-a)*sin(x)^m*cos(x)^(m+2) [if(a<b)]
b*sin(x)^m*cos(x)^m+(a-b)*sin(x)^(m+2)*cos(x)^m [if(a>=b)]
2° a*sin(x)^m*cos(x)^m-b*sin(x)^(m+2)*cos(x)^m = a*sin(x)^m*cos(x)^(m+2)-(b-a)*sin(x)^(m+2)*cos(x)^m
3° a*sin(x)^m*cos(x)^m-b*sin(x)^m*cos(x)^(m+2) = a*sin(x)^(m+2)*cos(x)^m-(b-a)*sin(x)^m*cos(x)^(m+2)
4° a*sin(x)^4 - a*cos(x)^4 = a*sin(x)^2 - a*cos(x)^2
以上四条化简公式分别为如下四个基础数学公式演化而来,但由于表达式的随机性和化简的不确定性,具有更好的效果。
1° sin(x)^2 + cos(x)^2 = 1
2° 1-sin(x)^2 = cos(x)^2
3° 1-cos(x)^2 = sin(x)^2
4° sin(x)^4 - cos(x)^4 = sin(x)^2 - cos(x)^2
具体实现,可用多次循环删除和循环添加实现。
优化重点:四条公式打乱优化,循环一定次数,记录每次的化简结果,输出时搜索最短结果。
首先定义变量record存储化简结果
private String record = "";
每次化简之后,记录化简结果,记录后及时清空record(注意,每次化简都应该去除一次零项)
for (int i = 0; i < list.size(); i++) {
flag = true;
printConstant(i);
printX(i);
printSinx(i);
printCosx(i);
}
if (list.size() == 0) {
record = record + "0";
}
link.add(record);
record = "";
最后输出时搜寻最短结果输出:
LinkedList<String> link = expression.getLink();
String least = link.get(0);
for (int i = 0; i < link.size(); i++) {
if (link.get(i).length() < least.length()) {
least = link.get(i);
}
}
System.out.println(least);
效果:
做到这个效果,你基本赢了。
四.进阶化简之合并同类因子(适用于第三次作业)
难度等级:III
实现思路:重写表达式类、项类、因子类的hashcode与equals方法,实现递归判断
在此我要说一下,我阅读了wsz大佬写的优化博客,也比较推荐他的符号化思路,但经过我仔细的思考,我并不赞同“递归调用equals方法为效率低下的方法”这一判断,因为递归判断嵌套因子实际上时线性扫描的过程,与符号化方法有着相同的时间复杂度。
为了逻辑清晰,特别定义了因子种类枚举类型:
public enum FactorType {
constant,x,sin,cos,expression
}
比较两个因子相等
@Override
public boolean equals(Object obj) {
if (obj instanceof Factor) {
Factor factor = (Factor) (obj);
if (factor.getType().equals(type)) {
if (type.equals(FactorType.constant)) {
return coeff.equals(factor.getCoeff());
} else if (type.equals(FactorType.x)) {
return exp.equals(factor.getExp());
} else if (type.equals(FactorType.sin)
|| type.equals(FactorType.cos)) {
return trInside.equals(factor.getTrInside())
&& exp.equals(factor.getExp());
} else {
return inside.equals(factor.getInside());
}
} else {
return false;
}
} else {
return false;
}
}
比较两个项相等
@Override
public boolean equals(Object obj) {
if (obj instanceof Term) {
Term term = (Term) (obj);
if (term.getFactors().size() != factors.size()) {
return false;
} else {
for (int i = 0; i < factors.size(); i++) {
if (!factors.get(i).equals(term.getFactors().get(i))) {
return false;
}
}
return true;
}
} else {
return false;
}
}
比较两个表达式相等
@Override
public boolean equals(Object obj) {
if (obj instanceof Expression) {
Expression expr = (Expression) (obj);
if (expr.getTerms().size() == 0 && terms.size() == 0) {
return true;
} else if (expr.getTerms().size() != terms.size()) {
return false;
} else {
for (int i = 0; i < terms.size(); i++) {
if (!expr.getTerms().get(i).equals(terms.get(i))) {
return false;
}
}
return true;
}
} else {
return false;
}
}
在判断因子相等之后,我们就可以进行项内因子合并工作了,大大减少项的长度。
五.进阶化简之合并同类项(适用于第三次作业)
难度等级:III
笔者认为,不做因子合并,合并同类项是没有意义的。
我们在合并完因子之后,在合并同类项之前还需要进行因子排序。
For instance:
5*sin(x)*cos(x)=cos(x)*5*sin(x)
emmm......不进行排序的话,你是判断不了这两个项相等的。
这里提供一种排序思路。
void sort() {
Collections.sort(factors, new Comparator<Factor>() {
@Override
public int compare(Factor o1, Factor o2) {
return compare_fac(o1, o2);
}
});
}
public static int compare_fac(Factor o1, Factor o2) {
if (o1.getType().equals(FactorType.sin) &&
o2.getType().equals(FactorType.sin)) {
return compare_fac(o1.getTrInside(), o2.getTrInside());
} else if (o1.getType().equals(FactorType.cos) &&
o2.getType().equals(FactorType.cos)) {
return compare_fac(o1.getTrInside(), o2.getTrInside());
} else if (o1.getType().equals(FactorType.expression) &&
o2.getType().equals(FactorType.expression)) {
if (o1.getInside().compareTo(o2.getInside()) > 0) {
return 1;
} else {
return -1;
}
} else if (o1.getType().equals(FactorType.constant)) {
return -1;
} else if (o1.getType().equals(FactorType.x)) {
if (o2.getType().equals(FactorType.constant)) {
return 1;
} else {
return -1;
}
} else if (o1.getType().equals(FactorType.sin)) {
if (o2.getType().equals(FactorType.constant)
|| o2.getType().equals(FactorType.x)) {
return 1;
} else {
return -1;
}
} else if (o1.getType().equals(FactorType.cos)) {
if (o2.getType().equals(FactorType.expression)) {
return -1;
} else {
return 1;
}
} else {
return 1;
}
}
当排完序之后再调用equals方法就可以判断两项相等并进行合并了。
当然在因子比较环节,遇到嵌套表达式因子的时候,也建议对嵌套的表达式因子进行用项排序
六.进阶化简之去表达式因子的括号(适用于第三次作业)
难度等级:IV
emmm.......第六部分其实是对第四、五部分的进一步优化
为什么要考虑去括号呢?
因为我写的equal函数认为:
sin((x))与sin(x),0与(0)是不equal的
去括号的方向在于,将表达式因子嵌套层数降至最低,或者转化为其他常数因子、幂函数因子或者三角函数因子。
去括号这一环节我不打算扔代码,因为这必须要结合自己的架构来,而且我写的很暴力,有点面向过程~~,希望敢于尝试这一部分的学弟学妹们独立思考。
七.终极化简之提取公因子(适用于第三次作业)
难度等级:看见没?特喵的有这么大
笔者在本优化环节付出了巨大心血,其实实现难度并不大,真正难的地方在于,如何在互测环节不出bug,并且在可控时间复杂度内做的漂亮。笔者花大量时间完成了提取公因子的工作,但害怕强测挂点或者互测成为大礼包,最后还是注释掉了提取公因子的优化函数。
我的实现思路:
1.遍历表达式第一个项中的因子,记录哪些项含有当前因子,以及含有该因子的项数。最后得到最大项数及对应的因子。
2.含有公因子项减去公因子的次数后,构建新的表达式因子,与公因子相乘得到新项,不含公因子的项不处理。
3.循环、递归直至找不到公因子。
4.去表达式因子的括号(如果这个实现不了,就不要提取公因子了)。
举例说明笔者的实现过程:
x*sin(x)^2*cos(x)+5*x*cos(x)^2 -----> x*(sin(x)^2*cos(x)+5*cos(x)^2)
x*(sin(x)^2*cos(x)+5*cos(x)^2) -----> x*(cos(x)*(sin(x)^2+5*cos(x)))
去括号
x*(cos(x)*(sin(x)^2+5*cos(x))) -----> x*cos(x)*(sin(x)^2+5*cos(x))
去括号一步尤其重要,笔者一开始没去括号,输入复杂些,输出甚至会更长。
本环节需要较多的独立思考,拒绝投放代码喂鱼。
八.文末寄语
本届的OO助教和课程组特别棒,今年的OO政策可谓非常合理了。感谢他们的辛勤付出,希望北航OO越来越好。(我太菜了)