这是学校perl课程结束时的大作业。
开始思考前,我上网搜寻了下,发现网上相应资料几乎没有,大多数都是求表达式某个点的导数值。而作业要求是求一个表达式的偏导式子,即输出也要是式子。
没办法,只能自己思考了(最终版本代码在最后(带注释,可直接跳跃看最终版代码,代码600行,建议复制到编辑器中观看))
开始阅读前建议点击下面链接观看3.5, 3.6, 3.7, 9.5, 9.6。因为写的很简单,不了解下数据结构的知识可能看不懂
B站数据结构视频
最开始思路
对于表达式求导,第一个目标是先做出来加减乘除的。由于观看了北大的表达式求值视频,所以一开始我就想通过建立表达式树来求导。
而建立表达式树又需要全括号形式(有多少个运算符,就有多少括号),所以最开始直接处理的是全括号形式式子。
首先建立一个树的结点类。
package Node; #表达式树的结点类
use strict;
sub new {
my $class = shift();
my $self = {};
$self->{"left"} = shift(); #左子结点
$self->{"right"} = shift(); #右子结点
$self->{"p"} = shift(); #父节点
$self->{"key"} = shift(); #储存常量、变量、运算符等信息
$self->{"type"} = shift(); #结点分两类,op(运算符结点)、leaf(叶子结点)(常量与变量)
$self->{"derivative"} = shift();#储存当前节点及其子树的导数
bless $self, $class;
return $self;
}
sub setLeft{ #设置结点左结点
my ($self) = @_;
my $left = Node->new(undef,undef,$self,undef,undef,undef);
$self->{"left"} = $left;
}
sub setRight{ #设置结点右结点
my ($self) = @_;
my $right = Node->new(undef,undef,$self,undef,undef,undef);
$self->{"right"} = $right;
}
1;
根据表达式建立表达式树,这里建议观看北京大学python版数据结构的表达式树构建。代码如下
sub buildParseTree{
my ($expresssion) = @_;
my @e = split //,$expresssion;
for(@e){
if($_ eq '('){
$cur->setLeft();
$cur = $cur->{"left"};
}elsif($_ eq ')'){
$cur = $cur->{"p"};
}elsif($_ =~ /[\+\-\*\/\^]/){
$cur->{"key"} = $_;
$cur->{"type"} = "op";
$cur->setRight();
$cur = $cur->{"right"};
}elsif($_ =~ /\w/){
$cur->{"key"} = $_;
$cur->{"type"} = "leaf";
$cur = $cur->{"p"};
}else{
print "Incorrect expression input\n";
}
}
}
然后对表达式树求导。求导后输出全括号形式结果,但是结果括号太多,还有(0-0)等很多没有必要的结构,代码如下。subExpression函数是编写的返回以给定结点为根的表达式
sub derivation{
my ($d_cur,$x) = @_;
if($d_cur->{"type"} eq "op"){
my $d_l = &derivation($d_cur->{"left"},$x);
my $d_r = &derivation($d_cur->{"right"},$x);
if($d_cur->{"key"} eq "+"){
$d_cur->{"derivative"} = "($d_l+$d_r)";
return $d_cur->{"derivative"};
}elsif($d_cur->{"key"} eq "-"){
$d_cur->{"derivative"} = "($d_l-$d_r)";
return $d_cur->{"derivative"};
}elsif($d_cur->{"key"} eq "*"){
my $e_l = &subExpression($d_cur->{"left"});
my $e_r = &subExpression($d_cur->{"right"});
$d_cur->{"derivative"} = "(($d_l*$e_r)+($d_r*$e_l))";
return $d_cur->{"derivative"};
}elsif($d_cur->{"key"} eq "/"){
my $e_l = &subExpression($d_cur->{"left"});
my $e_r = &subExpression($d_cur->{"right"});
$d_cur->{"derivative"} = "((($d_l*$e_r)-($d_r*$e_l))/($e_r^2))";
return $d_cur->{"derivative"};
}elsif($d_cur->{"key"} eq "^"){
my $e_l = &subExpression($d_cur->{"left"});
my $e_r = &subExpression($d_cur->{"right"});
$d_cur->{"derivative"} = "($e_r*($e_l^($e_r-1)))";
return $d_cur->{"derivative"};
}
}elsif($d_cur->{"type"} eq "leaf"){
if($d_cur->{"key"} eq "$x"){
$d_cur->{"derivative"} = 1;
return $d_cur->{"derivative"};
}else{
$d_cur->{"derivative"} = 0;
return $d_cur->{"derivative"};
}
}
}
最终代码如下:
#!usr/bin/perl -w
use strict;
use encoding 'utf8',STDIN=>'gb2312', STDOUT=>'gb2312';
#优先级说明:"+" => 1, "-" => 1, "*" => 2, "/" => 2, "^" => 3 输入表达式的时候根据优先级,必要括号不能少
#对于sin|cos|ln的处理,开始想到的是正则,但是(sin((())))这种n对括号、并且外面还有括号包住的麻烦的形式还是无能为力,就改成了遍历形式,比较万能
#处理过程
#1.对输入的表达式处理
#(1)去空格,处理负号,处理大于9数字等
#(2)处理sin|cos|ln(让他们在数组中只占一个位子、并且把他们变成和+等一样的双目运算符,便于建立表达式树)
#(3)表达式变后缀表达式,后缀再变成全括号表达式,让能建立表达式树
#2.表达式建立表达式树
#3.对表达式树求导
#4.对求导结果(全括号形式)建立导数表达式树
#5.化简导数表达式树(处理0*0,1*0等10几种情况)
#6.输出去掉部分括号的求导结果
#输入的表达式注意点
#1.exp()写成e^()形式
#2.所有单个字符的不要加括号,如sinx别写成sin(x),e^x别写成e^(x)
#3.负数要加括号,如(-1)、(-x)
#4.变量只能为一个字母,不能写成x1等(觉得这个没必要优化)
package Node; #表达式树的结点类
use strict;
sub new {
my $class = shift();
my $self = {};
$self->{"left"} = shift(); #左子结点
$self->{"right"} = shift(); #右子结点
$self->{"p"} = shift(); #父节点
$self->{"key"} = shift(); #储存常量、变量、运算符等信息
$self->{"type"} = shift(); #结点分两类,op(运算符结点)、leaf(叶子结点)(常量与变量)
$self->{"derivative"} = shift();#储存当前节点及其子树的导数
bless $self, $class;
return $self;
}
sub setLeft{ #设置结点左结点
my ($self) = @_;
my $left = Node->new(undef,undef,$self,undef,undef,undef);
$self->{"left"} = $left;
}
sub setRight{ #设置结点右结点
my ($self) = @_;
my $right = Node->new(undef,undef,$self,undef,undef,undef);
$self->{"right"} = $right;
}
1;
#用于实验的带有各种元素的表达式,非全括号形式
my $expresssion="3*x^20+4*x/(y+1)+1+y^(3*x)+3*sin((x+y)*x^2)+e^x+lnx+(-x)"; #输入的表达式
print "您输入的非全括号形式的式子: $expresssion\n\n";
my $x = "x"; #要改变求偏导的对象,改这里,如可改为y
#处理表达式的子程序
sub dealWithExpresssion{
my $e = shift @_;
$e =~ s/\(\-/\(0\-/; #处理负号
$e =~ s/\s+//; #处理空格
my @e = split //,$e;
my @e_merge; #储存第一轮处理后的数据
#下面for循环处理sin|cos|ln 与 连续数字让他们只占一个位子
for(my $i = 0; $i < $#e + 1;){#遍历分割后的数组
my ($s1, $s2, $s3) = ($e[$i], $e[$i+1], $e[$i+2]);
if(($s1 eq "s")&&($s2 eq "i")&&($s3 eq "n")){ #合并sin
push @e_merge,"sin";
$i += 3;
}elsif(($s1 eq "c")&&($s2 eq "o")&&($s3 eq "s")){#合并cos
push @e_merge,"cos";
$i += 3;
}elsif(($s1 eq "l")&&($s2 eq "n")){ #合并ln
push @e_merge,"ln";
$i += 2;
}elsif($s1 =~ /\d/){ #合并大于9的数字
my $num = $s1;
for(my $j = $i+1;$j<$#e+1;$j++){
$i = $j;
if($e[$j] =~ /\d/){$num .= $e[$j];}
else{last;}
}
push @e_merge,$num;
}else{
push @e_merge,$e[$i];
$i += 1;
}
}
# print "@e_merge\n";
my @e_merge_1; #储存第二轮处理后的数据
#下面的for循环处理sin|cos|ln让后期能够建立表达式树,做法为将sin|cos|ln转变为双目运算符
#这里最开始想到的正则,但是当(1+sin(x*(x*(x^y))))这种复杂sin,正则处理不会(括号太多,匹配不到对的)
for(my $i = 0; $i < $#e_merge + 1;){
if($e_merge[$i] =~ /sin|cos|ln/){
my $op = $e_merge[$i];
if($e_merge[$i+1] eq "("){ #当sin|cos|ln紧跟着(时,代表是要进行括号的匹配。直到右括号个数等于左括号时完成遍历
my $l_brackets = 0;
my $r_brackets = 0;
my $modulus;
for(my $j = $i+1;$j<$#e_merge+1;$j++){
$i = $j;
if($e_merge[$j] eq "("){ #为左括号时,代表左括号个数变量加一
++$l_brackets;
$modulus .= $e_merge[$j];
}elsif($e_merge[$j] eq ")"){ #为右括号时,代表左括号个数变量加一,并且当数量等于左括号时,结束循环
++$r_brackets;
$modulus .= $e_merge[$j];
if($r_brackets == $l_brackets){last;}
}else{$modulus .= $e_merge[$j];}
}
$i += 1;
push @e_merge_1,"(";
for(split //,$modulus){
push @e_merge_1,$_;
}
push @e_merge_1,$op;
for(split //,$modulus){
push @e_merge_1,$_;
}
push @e_merge_1,")";
}else{ #运行到这里代表是sinx这种简单形式
push @e_merge_1,"(";
push @e_merge_1,$e_merge[$i+1];
push @e_merge_1,$op;
push @e_merge_1,$e_merge[$i+1];
push @e_merge_1,")";
$i += 2;
}
}else{
push @e_merge_1,$e_merge[$i];
$i += 1;
}
}
# print "@e_merge_1\n";
#下面的操作是把表达式转化为全括号表达式
#1.转化为后缀表达式
#2.后缀转全括号表达式
#1.转化为后缀表达式,基本按北大视频来的
my %grade = ("(" => 0, "+" => 1, "-" => 1, "*" => 2, "/" => 2, "^" => 3, "sin" => 4, "cos" => 4, "ln" => 4);#记录优先级
my @stack; #数组可以做栈
my @e_merge_2; #储存第三轮处理后的数据
for(@e_merge_1){
if($_ =~ /[\+\-\*\/\^]/){
if($#stack >= 0){
my $temporary = pop @stack;
push @stack,$temporary;
while(($#stack >= 0)&&($grade{"$temporary"} >= $grade{"$_"})){
$temporary = pop @stack;
push @e_merge_2,$temporary;
if($#stack >= 0){
$temporary = pop @stack;
push @stack,$temporary;
}
}
}
push @stack,$_;
}elsif($_ =~ /sin|cos|ln/){
push @stack,$_;
}elsif($_ eq "("){
push @stack,$_;
}elsif($_ eq ")"){
my $pop = pop @stack;
while($pop ne "("){
push @e_merge_2,$pop;
$pop = pop @stack;
}
}else{
push @e_merge_2,$_;
}
}
while($#stack >= 0){
my $temporary = pop @stack;
push @e_merge_2,$temporary;
}
# print "@e_merge_2\n";
#2.后缀转全括号表达式,就是把北大视频的弹栈计算改成了弹栈加括号
for(@e_merge_2){
if(($_ =~ /sin|cos|ln/)||($_ =~ /[\+\-\*\/\^]/)){
my $r = pop @stack;
my $l = pop @stack;
push @stack,"($l$_$r)";
}else{
push @stack,$_;
}
}
my $full_brackets = pop @stack;
return $full_brackets;
}
$expresssion = &dealWithExpresssion($expresssion);
print "处理过并且是全括号形式的式子: $expresssion\n\n";
#建立表达式树子程序,也基本按北大视频来的,求导和化简求导都要建立表达式树
sub buildParseTree{
my ($expresssion,$cur) = @_;
my @e = split //,$expresssion; #分割为数组
my @e_merge; #储存处理过后的数组
for(my $i = 0;$i<$#e+1;){ #处理sin|cos|ln与连续数字让他们只占一个位子
my ($s1,$s2,$s3) = ($e[$i],$e[$i+1],$e[$i+2]);
if(($s1 eq "s")&&($s2 eq "i")&&($s3 eq "n")){
push @e_merge,"sin";
$i += 3;
}elsif(($s1 eq "c")&&($s2 eq "o")&&($s3 eq "s")){
push @e_merge,"cos";
$i += 3;
}elsif(($s1 eq "l")&&($s2 eq "n")){
push @e_merge,"ln";
$i += 2;
}elsif($s1 =~ /\d/){
my $num = $s1;
for(my $j = $i+1;$j<$#e+1;$j++){
$i = $j;
if($e[$j] =~ /\d/){$num .= $e[$j];}
else{last;}
}
push @e_merge,$num;
}else{
push @e_merge,$e[$i];
$i += 1;
}
}
#建立表达式树,和北大视频中一样
for(@e_merge){
if($_ eq '('){
$cur->setLeft();
$cur = $cur->{"left"};
}elsif($_ eq ')'){
$cur = $cur->{"p"};
}elsif($_ =~ /[\+\-\*\/\^]/){
$cur->{"key"} = $_;
$cur->{"type"} = "op";
$cur->setRight();
$cur = $cur->{"right"};
}elsif($_ =~ /sin|cos|ln/){ #sin|cos|ln与+-*/一样处理
$cur->{"key"} = $_;
$cur->{"type"} = "op";
$cur->setRight();
$cur = $cur->{"right"};
}elsif($_ =~ /\w+/){
$cur->{"key"} = $_;
$cur->{"type"} = "leaf";
$cur = $cur->{"p"};
}else{
print "全括号表达式有误\n";
}
}
}
my $e_root = Node->new(undef,undef,undef,undef,undef,undef); #表达式树根
$e_root->{"p"} = $e_root; #树根父亲为自己
&buildParseTree($expresssion,$e_root); #建立输入式子的表达式树
#中序遍历,写这个是观察树建立情况时用的
# sub printTree{
# my ($self) = @_;
# if(!(defined $self)){
# return;
# }
# &printTree($self->{"left"});
# my $m1 = $self->{"key"};
# my $m2 = $self->{"derivative"};
# if(defined $m2){
# print "$m1 $m2 ";
# }else{ print "$m1 "; }
# &printTree($self->{"right"});
# }
#遍历后的$cur为树跟的父亲,上面设置了它的父亲为自己,所以打印树的时候传入$cur
# &printTree($e_root);
#把 表达式树 还原成 全括号形式 的子程序,即相当于buildParseTree的相反
sub subExpression{
my ($d_cur) = @_;
if($d_cur->{"type"} eq "op"){ #结点为运算符类型时
if($d_cur->{"key"} =~ /sin|cos|ln/){#当为sin|cos|ln时,只需向一个方向递归(因为前面处理时把他们右边的复制到了左边),且返回值与正常运算符不同
my $e_r = &subExpression($d_cur->{"right"});
my $op = $d_cur->{"key"};
return "$op$e_r";
}else{
my $e_l = &subExpression($d_cur->{"left"});
my $e_r = &subExpression($d_cur->{"right"});
my $op = $d_cur->{"key"};
return "($e_l$op$e_r)";
}
}elsif($d_cur->{"type"} eq "leaf"){ #结点为叶子类型时
if($d_cur->{"key"} =~ /\-/ ){ #这里是为了防止后面求导简化后有负数形式存在
my $key = $d_cur->{"key"};
return "(0$key)";
}else{
return $d_cur->{"key"};
}
}
}
#求导子程序,要传2个参数,结点与求偏导的变量,也先分为"op"和"leaf"分别处理,其中op又分为很多情况
sub derivation{
my ($d_cur,$x) = @_;
if($d_cur->{"type"} eq "op"){
my $d_l = &derivation($d_cur->{"left"},$x); #左子树递归求导结果
my $d_r = &derivation($d_cur->{"right"},$x); #右子树递归求导结果
#以下分不同操作符进行不同规则求导
if($d_cur->{"key"} eq "+"){
$d_cur->{"derivative"} = "($d_l+$d_r)";
return $d_cur->{"derivative"};
}elsif($d_cur->{"key"} eq "-"){
$d_cur->{"derivative"} = "($d_l-$d_r)";
return $d_cur->{"derivative"};
}elsif($d_cur->{"key"} eq "*"){
my $e_l = &subExpression($d_cur->{"left"});
my $e_r = &subExpression($d_cur->{"right"});
$d_cur->{"derivative"} = "(($e_r*$d_l)+($e_l*$d_r))";
return $d_cur->{"derivative"};
}elsif($d_cur->{"key"} eq "/"){
my $e_l = &subExpression($d_cur->{"left"});
my $e_r = &subExpression($d_cur->{"right"});
$d_cur->{"derivative"} = "((($d_l*$e_r)-($d_r*$e_l))/($e_r^2))";
return $d_cur->{"derivative"};
}elsif($d_cur->{"key"} eq "^"){ #幂函数与指数函数都是^符号,分情况讨论
my $e_l = &subExpression($d_cur->{"left"});
my $e_r = &subExpression($d_cur->{"right"});
if($e_l =~ /$x/){ #x^a形式
$d_cur->{"derivative"} = "($e_r*($e_l^($e_r-1)))";
return $d_cur->{"derivative"};
}elsif(($e_l eq "e") && ($e_r =~ /$x/)){ #e^x形式
$d_cur->{"derivative"} = "(($e_l^$e_r)*$d_r)";
return $d_cur->{"derivative"};
}elsif($e_r =~ /$x/){ #a^x形式
$d_cur->{"derivative"} = "((ln$e_l*($e_l^$e_r))*$d_r)";
return $d_cur->{"derivative"};
}else{ #常数
$d_cur->{"derivative"} = 0;
return 0;
}
}elsif($d_cur->{"key"} eq "sin"){#sin求导
my $e_r = &subExpression($d_cur->{"right"});#sin|cos|ln是处理过的,只取一边
if($e_r =~ /$x/){ #sin式子中包含要求偏导的变量
$d_cur->{"derivative"} = "(cos$e_r*$d_r)";
return $d_cur->{"derivative"};
}else{ #常量
$d_cur->{"derivative"} = 0;
return 0;
}
}elsif($d_cur->{"key"} eq "cos"){#cos求导
my $e_r = &subExpression($d_cur->{"right"});
if($e_r =~ /$x/){ #cos式子中包含要求偏导的变量
$d_cur->{"derivative"} = "((0-sin$e_r)*$d_r)";
return $d_cur->{"derivative"};
}else{ #常量
$d_cur->{"derivative"} = 0;
return 0;
}
}elsif($d_cur->{"key"} eq "ln"){#ln求导
my $e_r = &subExpression($d_cur->{"right"});
if($e_r =~ /$x/){ #ln式子中包含要求偏导的变量
$d_cur->{"derivative"} = "((1/$e_r)*$d_r)";
return $d_cur->{"derivative"};
}else{ #常量
$d_cur->{"derivative"} = 0;
return 0;
}
}else{
print "建立的表达式树有误\n";
}
}elsif($d_cur->{"type"} eq "leaf"){#叶子结点分2种情况
if($d_cur->{"key"} eq "$x"){#包含要求偏导的变量
$d_cur->{"derivative"} = 1;
return $d_cur->{"derivative"};
}else{ #不包含要求偏导的变量
$d_cur->{"derivative"} = 0;
return $d_cur->{"derivative"};
}
}
}
my $e_derivation = &derivation($e_root,"$x");#储存未化简的求导全括号结果
print "全括号形式的求导结果: $e_derivation\n\n";
my $d_root = Node->new(undef,undef,undef,undef,undef,undef);#求导结果的表达式树根
$d_root->{"p"} = $d_root;
$e_derivation = &dealWithExpresssion($e_derivation);#处理全括号形式的求导结果
&buildParseTree($e_derivation,$d_root); #建立求导结果的表达式树
#简化求导结果的子程序,只遍历op类型结点,根据其左右结点类型分4大类,每一类又细分
#主要化简0*x,1*x,1+3等10几种情况
sub simplify{
my ($d_cur) = @_;
my $l = $d_cur->{"left"};
my $r = $d_cur->{"right"};
my $type_l = $l->{"type"};
my $type_r = $r->{"type"};
if(($type_l eq "leaf")&&($type_r eq "leaf")){#左右结点都为叶子结点时
if($d_cur->{"key"} eq "+"){
if(($l->{"key"} =~ /\d+/) && ($r->{"key"} =~ /\d+/)){ #数字+数字
$d_cur->{"key"} = $l->{"key"}+$r->{"key"};
$d_cur->{"type"} = "leaf";
($l,$r) = (undef,undef);
}elsif($l->{"key"} eq "0"){ #0+变量
$d_cur->{"key"} = $r->{"key"};
$d_cur->{"type"} = "leaf";
($l,$r) = (undef,undef);
}elsif($r->{"key"} eq "0"){ #变量+0
$d_cur->{"key"} = $l->{"key"};
$d_cur->{"type"} = "leaf";
($l,$r) = (undef,undef);
}
}elsif($d_cur->{"key"} eq "-"){
if(($l->{"key"} =~ /\d+/) && ($r->{"key"} =~ /\d+/)){ #数字-数字
$d_cur->{"key"} = $l->{"key"}-$r->{"key"};
$d_cur->{"type"} = "leaf";
($l,$r) = (undef,undef);
}elsif($l->{"key"} eq "0"){ #0-数字
$d_cur->{"key"} = $r->{"key"};
$d_cur->{"type"} = "leaf";
($l,$r) = (undef,undef);
}elsif($r->{"key"} eq "0"){ #数字-0
$d_cur->{"key"} = $l->{"key"};
$d_cur->{"type"} = "leaf";
($l,$r) = (undef,undef);
}
}elsif($d_cur->{"key"} eq "*"){
if(($l->{"key"} =~ /\d+/) && ($r->{"key"} =~ /\d+/)){ #数字*数字
$d_cur->{"key"} = $l->{"key"}*$r->{"key"};
$d_cur->{"type"} = "leaf";
($l,$r) = (undef,undef);
}elsif( ($l->{"key"} eq "0") || ($r->{"key"} eq "0") ){ #左右中出现0
$d_cur->{"key"} = 0;
$d_cur->{"type"} = "leaf";
($l,$r) = (undef,undef);
}elsif($l->{"key"} eq "1"){ #1*变量
$d_cur->{"key"} = $r->{"key"};
$d_cur->{"type"} = "leaf";
($l,$r) = (undef,undef);
}elsif($r->{"key"} eq "1"){ #变量*1
$d_cur->{"key"} = $l->{"key"};
$d_cur->{"type"} = "leaf";
($l,$r) = (undef,undef);
}
}elsif($d_cur->{"key"} eq "/"){ #0/任何
if($l->{"key"} eq "0"){
$d_cur->{"key"} = 0;
$d_cur->{"type"} = "leaf";
($l,$r) = (undef,undef);
}
}elsif($d_cur->{"key"} eq "^"){
if(($l->{"key"} =~ /\d+/) && ($r->{"key"} =~ /\d+/)){ #数字^数字
$d_cur->{"key"} = $l->{"key"}**$r->{"key"};
$d_cur->{"type"} = "leaf";
($l,$r) = (undef,undef);
}elsif($l->{"key"} eq "1"){ #1^任意
$d_cur->{"key"} = 1;
$d_cur->{"type"} = "leaf";
($l,$r) = (undef,undef);
}elsif($r->{"key"} eq "1"){ #任意^1....输入时可以避免输入任意^1产生任意^0的情况
$d_cur->{"key"} = $l->{"key"};
$d_cur->{"type"} = "leaf";
($l,$r) = (undef,undef);
}
}
return;
}elsif(($type_l eq "op")&&($type_r eq "op")){#左右结点都为运算符结点时
&simplify($l);
&simplify($r);
}else{
if($type_l eq "leaf"){ #右结点为运算符结点,左结点为叶子结点时
&simplify($r);
if($d_cur->{"key"} eq "+"){
if($l->{"key"} eq "0"){
$d_cur->{"key"} = $r->{"key"}; #0+式子
$d_cur->{"type"} = $r->{"type"};
$d_cur->{"left"} = $r->{"left"};
$d_cur->{"right"} = $r->{"right"};
}
}elsif($d_cur->{"key"} eq "*"){
if($l->{"key"} eq "0"){ #0*式子
$d_cur->{"key"} = 0;
$d_cur->{"type"} = "leaf";
($l,$r) = (undef,undef);
}elsif($l->{"key"} eq "1"){ #1*式子
$d_cur->{"key"} = $r->{"key"};
$d_cur->{"type"} = $r->{"type"};
$d_cur->{"left"} = $r->{"left"};
$d_cur->{"right"} = $r->{"right"};
}
}elsif($d_cur->{"key"} eq "/"){ #0/式子
if($l->{"key"} eq "0"){
$d_cur->{"key"} = 0;
$d_cur->{"type"} = "leaf";
($l,$r) = (undef,undef);
}
}elsif($d_cur->{"key"} eq "^"){
if($l->{"key"} eq "1"){ #1^式子
$d_cur->{"key"} = 1;
$d_cur->{"type"} = "leaf";
($l,$r) = (undef,undef);
}
}
}else{ #左结点为运算符结点,右结点为叶子结点时
&simplify($l);
if($d_cur->{"key"} eq "+"){
if($r->{"key"} eq "0"){ #式子+0
$d_cur->{"key"} = $l->{"key"};
$d_cur->{"type"} = $l->{"type"};
$d_cur->{"left"} = $l->{"left"};
$d_cur->{"right"} = $l->{"right"};
}
}elsif($d_cur->{"key"} eq "*"){
if($r->{"key"} eq "0"){ #式子*0
$d_cur->{"key"} = 0;
$d_cur->{"type"} = "leaf";
($l,$r) = (undef,undef);
}elsif($r->{"key"} eq "1"){ #式子*1
$d_cur->{"key"} = $l->{"key"};
$d_cur->{"type"} = $l->{"type"};
$d_cur->{"left"} = $l->{"left"};
$d_cur->{"right"} = $l->{"right"};
}
}elsif($d_cur->{"key"} eq "-"){
if($r->{"key"} eq "0"){ #式子-0
$d_cur->{"key"} = $l->{"key"};
$d_cur->{"type"} = $l->{"type"};
$d_cur->{"left"} = $l->{"left"};
$d_cur->{"right"} = $l->{"right"};
}
}elsif($d_cur->{"key"} eq "^"){
if($r->{"key"} eq "1"){ #式子^1
$d_cur->{"key"} = $l->{"key"};
$d_cur->{"type"} = $l->{"type"};
$d_cur->{"left"} = $l->{"left"};
$d_cur->{"right"} = $l->{"right"};
}
}
}
}
}
&simplify($d_root);
my $simplify_e_derivation = &subExpression($d_root);
for(1..10){#化简一边可能会有新的出现,多化简几次,这里没有想到很好的控制简化结束的方法,所以就粗暴的用了循环10次
&simplify($d_root);
$simplify_e_derivation = &subExpression($d_root);
}
#去括号子程序(根据运算符优先级),相对于subExpression函数有所变动
my %grade = ("+" => 1, "-" => 1, "*" => 2, "/" => 2, "^" => 3, "sin" => 4, "cos" => 4, "ln" => 4);#记录优先级
sub super_subExpression{
my ($d_cur) = @_;
if($d_cur->{"type"} eq "op"){ #结点为运算符类型时
if($d_cur->{"key"} =~ /sin|cos|ln/){#当为sin|cos|ln时,只需向一个方向递归,且返回值与正常运算符不同
my $e_r = &super_subExpression($d_cur->{"right"});
my $op = $d_cur->{"key"};
if(length $e_r == 1){ #sinx这种情况不需要加括号
return "$op$e_r";
}else{return "$op($e_r)";}
}else{
my $e_l = &super_subExpression($d_cur->{"left"});
my $e_r = &super_subExpression($d_cur->{"right"});
my $op = $d_cur->{"key"};
if($op =~ /[\*\/]/){ #当运算符结点的左或右结点为优先级比他们低的运算符结点时,左或右结点要加括号
if($d_cur->{"left"}->{"key"} =~ /[\+\-]/){$e_l = "($e_l)"}
if($d_cur->{"right"}->{"key"} =~ /[\+\-]/){$e_r = "($e_r)"}
return "$e_l$op$e_r";
}elsif($op eq "^"){
if($d_cur->{"left"}->{"key"} =~ /[\+\-\*\/]/){$e_l = "($e_l)"}
if($d_cur->{"right"}->{"key"} =~ /[\+\-\*\/]/){$e_r = "($e_r)"}
return "$e_l$op$e_r";
}else{
return "$e_l$op$e_r";
}
}
}elsif($d_cur->{"type"} eq "leaf"){#结点为叶子类型时
if($d_cur->{"key"} =~ /\-/ ){
my $key = $d_cur->{"key"};
return "($key)";
}else{
return $d_cur->{"key"};
}
}
}
$simplify_e_derivation = &super_subExpression($d_root);
print "化简并且去掉部分括号后的偏导数:$simplify_e_derivation\n\n";