数据结构表达式的两种计算方法.docx
《数据结构表达式的两种计算方法.docx》由会员分享,可在线阅读,更多相关《数据结构表达式的两种计算方法.docx(24页珍藏版)》请在冰豆网上搜索。
![数据结构表达式的两种计算方法.docx](https://file1.bdocx.com/fileroot1/2023-6/1/e9eb9e0a-adc6-411d-a16f-b7964330ccb7/e9eb9e0a-adc6-411d-a16f-b7964330ccb71.gif)
数据结构表达式的两种计算方法
数据结构表达式的两种计算方法
一、设计思想
(一)先将输入的中缀表达式转为后缀再计算的设计思想
我们所熟知的计算表达式为中缀表达式,这之中包含运算符的优先级还有括号,这对我们来说已经习以为常了,但是在计算机看来,这是非常复杂的一种表达式。
因此我们需要有一种更能使计算机理解的不用考虑优先级也不包括括号的表达式,也就是后缀表达式。
我们可以借助栈将其实现。
首先,我们需要将中缀表达式转换为后缀表达式,这也是这个算法的关键之处。
我们将创建两个栈,一个是字符型的,用来存放操作符;另一个是浮点型的,存放操作数。
接着,开始扫描输入的表达式,如果是操作数直接进入一个存放后缀表达式的数组,而操作符则按照优先级push进栈(加减为1,乘除为2),若当前操作符优先级大于栈顶操作符优先级或栈为空,push进栈,而当其优先级小于等于栈顶操作符优先级,则从栈内不断pop出操作符并进入后缀表达式数组,直到满足条件,当前操作符才能push进栈。
左括号无条件入栈,右括号不入栈,而不断从栈顶pop出操作符进入后缀表达式数组,直到遇到左括号后,将其pop出栈。
这样当扫描完输入表达式并从操作符栈pop出残余操作符后并push进栈,后缀表达式数组中存放的就是我们所需要的后缀表达式了。
扫描后缀表达式数组,若是操作数,将其转换为浮点型push进数栈;若是操作符,则连续从数栈中pop出两个数做相应运算,将结果push进数栈。
当扫描完数组后,数栈顶便为最终结果,将其pop出,输出结果。
(二)一边扫描一边计算的设计思想
由于第一种算法需要进行两遍扫描,因此在性能上不会十分优秀。
而此种算法只用扫描一遍,当扫描完输入的表达式后便可以直接输出最终结果。
是第一种算法的改进版,性能上也得到提升,与第一种算法所不同的是其需要同时使用两个栈,一个操作符栈,一个数栈。
当扫描表达式时,若是操作数则将其转换为浮点型后直接push进数栈,而若是操作符则按照优先级规则push进操作符栈(加减为1,乘除为2),若当前操作符优先级大于栈顶操作符优先级或栈为空,push进栈,而当其优先级小于等于栈顶操作符优先级,则从栈内不断pop出操作符,直到满足条件,当前操作符才能push进栈。
左括号无条件入栈,右括号不入栈,而不断从栈顶pop出操作符,直到遇到左括号后,将其pop出栈。
这中间pop出操作符后直接从数栈中pop出两个数并计算,将结果push进数栈。
括号的处理与第一个算法相同。
扫描完成后,从操作符栈pop出残余操作符,从数栈中pop出两个数并计算并进行计算,将结果push进数栈。
数栈顶便为最终结果,将其pop出,输出结果。
两种算法各有各的优缺点,第一种算法过程比较清晰,使我们能够更加容易理解栈的使用规则,但是其性能不如第二种。
第二种算法相比第一种来说性能提高了,但是理解起来就不如第一种那么清晰了。
二、算法流程图
以下是先将输入的中缀表达式转为后缀再计算算法:
图1中缀转后缀算法流程图
以下是一边扫描一边计算算法:
图2一边扫描一边计算算法流程图
三、源代码
以下为先将输入的中缀表达式转为后缀再计算算法代码:
#include""
#include""
/*定义存放操作符的结构体*/
structOpStrack
{
charop[100];/*存放操作符的数组*/
inttop;/*栈顶索引*/
intlevel[100];/*存放操作符优先级的数组*/
}OpStrackArray;
/*定义存放语素的结构体*/
structOdStrack
{
floatod[100];/*存放操作数的数组*/
inttop;/*栈顶索引*/
}OdStrackArray;
/*声明需要用到的函数*/
intjudge(char,char[],int,int);
intstackIsEmpty();
voidpushOp(char,int);
charpopOp();
voidpushOd(float);
floatpopOd();
voidcompute(char);
/*主函数*/
voidmain()
{
chardata[100];/*声明存放中缀表达式的字符串*/
chartempdata[200];/*声明存放后缀表达式的字符串*/
inti=0;/*作为遍历字符串的索引*/
intj=0;/*作为输出后缀表达式的索引*/
intz=0;/*作为存放后缀表达式的索引*/
intk=0;/*作为将后缀表达式赋给临时转float的索引*/
inteq=0;/*判断括号是否正确的条件*/
floatresult;/*声明存放最终结果的float*/
scanf("%s",data);/*输入中缀表达式*/
/*中缀转后缀,并将结果放入tempdata*/
while(data[i]!
='\0')
{
if((data[i]>='0'&&data[i]<='9')||data[i]=='.')/*如果是语素则存放至temp数组*/
{
tempdata[z]=data[i];
z++;
}
else
{
switch(data[i])
{
case'+':
z+=judge(data[i],tempdata,i,1);
break;
case'-':
z+=judge(data[i],tempdata,i,1);/*加减优先级为1*/
break;
case'*':
z+=judge(data[i],tempdata,i,2);/*乘除优先级为2*/
break;
case'/':
/*返回出栈操作符的数量,以便将z索引向后推相应步*/
z+=judge(data[i],tempdata,i,2);
break;
case'(':
pushOp(data[i],-1);/*'('直接入栈,但入栈后优先级最小*/
eq++;/*有一个'('eq+1*/
break;
case')':
while[]!
='(')/*')'不入栈*/
{
/*不断弹出操作符并进入后缀表达式数组,直到遇到'('*/
tempdata[z]=popOp();
z++;/*索引+1*/
/*如果发现还没碰到与之匹配的'('时栈已空则说明表达式不合法*/
if(stackIsEmpty()==1)
{
printf("Expressionisnotvalid!
Pressanykeytocontinue...");
getch();
return;
}
}
popOp();/*弹出'('*/
eq--;/*如有与'('配队的')'则eq-1*/
break;
}
}
i++;
}
if(eq!
=0)/*如果eq!
=0说明'('与')'不能完全配队,输入的表达式不合法*/
{
printf("Expressionisnotvalid!
Pressanykeytocontinue...");
getch();
return;
}
/*将操作符栈内存留的操作符放入tempdata*/
while(stackIsEmpty()==0)
{
/*如果操作符栈不空则不断弹出操作符并进入后缀表达式数组*/
tempdata[z]=popOp();
z++;
}
/*扫描后缀表达式,计算后放入操作数栈*/
while(k{
chartemp[20]={'\0'};/*声明临时的存放操作数的数组并附初值*/
intt=0;/*作为temp数组的索引*/
floatf;/*存放转换为float的数*/
/*如果是语素则存放至temp数组*/
while((tempdata[k]>='0'&&tempdata[k]<='9')||tempdata[k]=='.')
{
temp[t]=tempdata[k];
k++;
t++;
}
if(temp[0]!
='\0')/*判断temp数组是否为空,是则将其转换为float并压栈*/
{
f=atof(temp);/*字符串转float*/
pushOd(f);/*操作数入栈*/
}
if(tempdata[k]=='+'||tempdata[k]=='-'||tempdata[k]=='*'||tempdata[k]=='/')
{
compute(tempdata[k]);/*如果扫描到操作符则作相应计算*/
}
k++;
}
/*输出后缀表达式*/
printf("Thepostfixexpressionis:
");
for(j=0;j{
printf("%c",tempdata[j]);
}
printf("\n");
/*从操作数栈内弹出最终结果并输出*/
result=popOd();
printf("Theresultis:
%.2f",result);/*结果取两位小数*/
printf("\n");
printf("Pressanykeytocontinue...");
getch();
}
/*判断操作符优先级并作出相应处理,返回出栈操作符数量*/
intjudge(charop,chartempdata[],intindex,intlevel)
{
intcont=0;
if(stackIsEmpty()==1||[]{
/*如果栈为空或栈顶操作符优先级小于当前操作符优先级则将其压栈*/
pushOp(op,level);
cont++;/*使z索引向后推1*/
}
else
{
while(stackIsEmpty()!
=1&&[]>=level)
{
/*操作符不断出栈并入后缀表达式数组直到栈为空或栈顶操作符优先级大于等于当前操作符优先级*/
tempdata[index]=popOp();
index++;/*后缀表达式数组索引+1*/
cont++;/*出栈操作符数量+1*/
}
pushOp(op,level);/*将当前操作符压栈*/
}
returncont;
}
/*判断操作符栈是否为空,是则返回1,否返回0*/
intstackIsEmpty()
{
if[0]=='\0')
{
return1;
}
return0;
}
/*将操作符压入栈*/
voidpushOp(charop,intlevel)
{
[]=op;/*操作符进入数组*/
[]=level;/*为对应操作符附优先级*/
++;/*top索引+1*/
}
/*操作符出栈,返回栈顶操作符*/
charpopOp()
{
charc=[];/*取得栈顶操作符*/
[]='\0';
/*分别将op数组与level数组还原为默认值*/
[]=0;
;/*top索引-1*/
returnc;/*返回栈顶操作符*/
}
/*将操作数压入栈*/
voidpushOd(floatf)
{
[]=f;/*操作数进入数组*/
++;/*top索引+1*/
}
/*操作符出栈,返回栈顶操作数*/
floatpopOd()
{
floatf=[];/*取得栈顶操作数*/
[]=;/*将od数组还原为默认值*/
;/*top索引-1*/
returnf;/*返回栈顶操作数*/
}
/*根据传入操作符运算,并将结果压入操作数栈*/
voidcompute(charop)
{
floattempresult;/*临时计算结果*/
floatod1=popOd();
floatod2=popOd();/*连续pop出两个操作数*/
switch(op)/*根据传入操作符计算*/
{
case'+':
tempresult=od2+od1;
break;
case'-':
tempresult=od2-od1;
break;
case'*':
tempresult=od2*od1;
break;
case'/':
tempresult=od2/od1;
break;
}
pushOd(tempresult);/*将计算结果压栈*/
}
以下为一边扫描一边计算算法代码:
#include""
#include""
/*定义存放操作符的结构体*/
structOpStrack
{
charop[100];/*存放操作符的数组*/
inttop;/*栈顶索引*/
intlevel[100];/*存放操作符优先级的数组*/
}OpStrackArray;
/*定义存放语素的结构体*/
structOdStrack
{
floatod[100];/*存放操作数的数组*/
inttop;/*栈顶索引*/
}OdStrackArray;
/*声明需要用到的函数*/
voidpushOp(char,int);
charpopOp();
voidpushOd(float);
floatpopOd();
intstackIsEmpty();
voidjudge(char,int);
voidcompute(char);
/*主函数*/
voidmain()
{
chardata[100];/*声明存放中缀表达式的字符串*/
inti=0;/*作为遍历字符串的索引*/
inteq=0;/*判断括号是否正确的条件*/
floatresult;/*声明存放最终结果的float*/
scanf("%s",data);/*输入中缀表达式*/
/*扫描输入的表达式并作出相应处理*/
while(data[i]!
='\0')
{
chartemp[20]={'\0'};/*声明临时的存放操作数的数组并附初值*/
intj=0;/*作为temp数组的索引*/
floatf;/*存放转换为float的数*/
while((data[i]>='0'&&data[i]<='9')||data[i]=='.')/*如果是语素则存放至temp数组*/
{
temp[j]=data[i];
j++;
i++;
}
if(temp[0]!
='\0')/*判断temp数组是否为空,是则将其转换为float并压栈*/
{
f=atof(temp);/*字符串转float*/
pushOd(f);/*操作数入栈*/
}
switch(data[i])
{
case'+':
judge(data[i],1);
break;
case'-':
judge(data[i],1);/*加减优先级为1*/
break;
case'*':
judge(data[i],2);
break;
case'/':
judge(data[i],2);/*乘除优先级为2*/
break;
case'(':
pushOp(data[i],-1);/*'('直接入栈,但入栈后优先级最小*/
eq++;/*有一个'('eq+1*/
break;
case')':
while[]!
='(')/*')'不入栈*/
{
compute(popOp());/*不断弹出操作符并计算,直到遇到'('*/
/*如果发现还没碰到与之匹配的'('时栈已空则说明表达式不合法*/
if(stackIsEmpty()==1)/
{
printf("Expressionisnotvalid!
Pressanykeytocontinue...");
getch();
return;
}
}
popOp();/*弹出'('*/
eq--;/*如有与'('配队的')'则eq-1*/
break;
}
i++;
}
/*扫描操作符栈内残余操作符并做相应计算*/
while(stackIsEmpty()==0)
{
compute(popOp());/*如果操作符栈不空则弹出操作符并计算*/
}
/*从操作数栈内弹出最终结果并输出*/
if(eq==0)/*如果eq=0说明'('与')'可以完全配队,输入的表达式合法*/
{
result=popOd();
printf("Theresultis:
%.2f",result);/*结果取两位小数*/
}
else/*如果eq!
=0说明'('与')'不能完全配队,输入的表达式不合法*/
{
printf("Expressionisnotvalid!
Pressanykeytocontinue...");
}
printf("\n");
printf("Pressanykeytocontinue...");
getch();
}
/*将操作符压入栈*/
voidpushOp(charop,intlevel)
{
[]=op;/*操作符进入数组*/
[]=level;/*为对应操作符附优先级*/
++;/*top索引+1*/
}
/*操作符出栈,返回栈顶操作符*/
charpopOp()
{
charc=[];/*取得栈顶操作符*/
[]='\0';
[]=0;/*分别将op数组与level数组还原为默认值*/
;/*top索引-1*/
returnc;/*返回栈顶操作符*/
}
/*将操作数压入栈*/
voidpushOd(floatf)
{
[]=f;/*操作数进入数组*/
++;/*top索引+1*/
}
/*操作符出栈,返回栈顶操作数*/
floatpopOd()
{
floatf=[];/*取得栈顶操作数*/
[]=;/*将od数组还原为默认值*/
;/*top索引-1*/
returnf;/*返回栈顶操作数*/
}
/*判断操作符栈是否为空,是则返回1,否返回0*/
intstackIsEmpty()
{
if[0]=='\0')
{
return1;
}
return0;
}
/*判断操作符优先级作出相应处理*/
voidjudge(charop,intlevel)
{
if(stackIsEmpty()==1||[]{
/*如果栈为空或栈顶操作符优先级小于当前操作符优先级则将其压栈*/
pushOp(op,level);
}
else
{
while(stackIsEmpty()!
=1&&[]>=level)
{
/*操作符不断出栈并计算直到栈为空或栈顶操作符优先级大于等于当前操作符优先级*/
compute(popOp());
}
pushOp(op,level);/*将当前操作符压栈*/
}
}
/*根据传入操作符运算,并将结果压入操作数栈*/
voidcompute(charop)
{
floattempresult;/*临时计算结果*/
floatod1=popOd();
floatod2=popOd();/*连续pop出两个操作数*/
switch(op)/*根据传入操作符计算*/
{
case'+':
tempresult=od2+od1;
break;
case'-':
tempresult=od2-od1;
break;
case'*':
tempresult=od2*od1;
break;
case'/':
tempresult=od2/od1;
break;
}
pushOd(tempresult);/*将计算结果压栈*/
}
四、运行结果
图3中缀转后缀算法运算结果
图4一边扫描一边计算算法运算结果
五、遇到的问题及解决
这部分我主要遇到了如下几个问题,其内容与解决方法如下所列:
中缀转后缀算法:
问题1:
扫描完成后输出的后缀表达式会带有’(’,从而导致计算不正确。
解决方法:
在遇到’)’后,程序会调用pop方法不断从操作符栈内弹出操作符存入后缀表达式数组,直到遇到’(’。
就是在这里,在遇到’(’后应该直接弹出’(’而不应该将其存入后缀表达式数组,改正后后缀表达式中自然就不再存在’(’。
问题2:
虽然解决了第一个问题,但是有时后缀表达式仍然输出不正确,不是掉个操作符就是少个数字。
解决方法:
经过深入研究,我最终发现凡是当遇到’)’需要连续从操作符栈pop出操作符时就会出现这个问题。
原来输入表达式字符串和存储后缀表达式字符串的索引相同,因为我想数字与数字之间有操作符自然分割,但是在这种情况下,输入表达式字符串索引的确是向后推1,而存储后缀表达式字符串的索引就不止向后推1了,需要根据pop出的操作符个数来确定。
因此我声明了另一个索引z,而判断操作符入栈规则的方法也将返回pop出操作符的数量,再将z索引向后推相应的个数这个问题就解决了。
问题3:
当输入表达式不正确时,程序将出现不同问题。
例如,’)’出现在了’(’前面,程序将如图所示错误:
图5问题3
而缺少’)’与’(’匹配时将不会得到正确结果。
解决方法:
在遇到’)’后不断pop出操作符栈内操作符,当栈为空时还没出现’(’,则输出“Expressionisnotvalid!
”并return主方法;另声明一个变量eq,当遇到’(’时eq+1,遇到’)’时eq-1,扫描结束后判断eq是否为0,是则说明表达式合法,程序继续运行,不是则说明不合法,输出“Expressionisnotvalid!
”并return主方法。
一边扫描一边计算算法:
问题4:
输入的数经转换为float型后与原来不符。
解决方法:
扫描输入表达式时需要从中分离出操作数,push进数栈,而分离出来的数字是一个字符串,要存放在一个临时字符串数组中,之后通过函数将其转换为float型,push进数栈。
就是这时再处理完一个数后,没有将临时数组清空而导致下一次数字转换的错误。
在每次处理后,将数组清空,这个问题自然也就引刃而解啦。
六、心得体会
通过这次作业,让我对栈的原理有了更深的理解。
而在作业中对栈的使用也是我体会到栈这个结构的强大之处,如果运用得当,可以使我们写的程序更加简单清晰,性能也更加优良。
正是因此,让我懂得了一个好的算法,可以使平时我们看上去很难解决的问题轻而易举地得到解决。
从大局上看,编写程序之前一定要先思考解决这一问题的思想,实现这项功能的算法,不能一开始就考虑细节上的问题。
而编写代码时,则需要好好考虑好细节,思考各种各样情况,不能再想着整体上的实现。
不然就将使自己的思维混乱,体现在程序上就是结构混乱,不仅别人很难看懂,自己也很容易被