【暖*墟】#栈# 算术表达式的求值

【利用栈实现算术表达式的求值】 

//https://blog.csdn.net/YanHS_/article/details/78702294

[问题描述]

利用栈实现算术表达式的求值。可以简单一些,假设表达式中含有一位正整数,

以及+、-、*、/、(、)。(难易程度:中)

[实验内容及要求]

1、表达式以字符串形式输入,并以‘#’开始和结束。

(‘#’也作为算法来处理)。如输入:#6+3*(9-7)-8/2#

2、能够有效判别表达式的输入格式是否有误(如缺失操作数、括号不匹配等错误),

若输入格式错误,输出错误提示。

[测试数据]

1、#6+3*(9-7)-8/2#

2、#(8-2)/(3-1)*(9-6)#

3、#5+a*(8-4)/2#

4、#5+(7-3)*6#

[题目简析]

本题中给出了描述,运算数均为“一位正整数”,

这个条件大大降低了本题的难度,可以将数字和字符一同输入,然后再进行分开处理。

考虑用字符串作为表达式的存储方式,需要进行对数字的处理时再单独拿出来转化处理。

[基本步骤]
  1. 中缀表达式——>后缀表达式(逆波兰表达式)(利用栈)
  2. 后缀表达式求解(利用栈)

[程序细节]

  • 表达式通过字符串的方式输入和存储
  • 使用的栈类型为顺序栈
  • 数字以字符型变量存储与整型变量存储的区别和相互转化
  • [实现过程] 先给出需要用到的功能性子函数。
  • 然后使用顺序栈,以下给出顺序栈的结构类型定义和基本操作的子函数定义


顺序栈结构类型定义:

    typedef struct
    {
    	char data[50];
    	int top;
    }stack;//顺序栈类型定义


栈的基本操作:

    void newstack(stack *&S)//初始化顺序栈
    {
    	S=(stack*)malloc(sizeof(stack));
    	S->top=-1;
    } 
     
    int push(stack*S,char e)//进栈 
    {
    	if(S->top>49)
    	{
    		printf("in error!\n");
    		return 0;
    	}
    	else
    	{
    		S->data[++S->top]=e;
    		return 1;
    	}
    } 
     
    int empty(stack*S)//判断栈空
    {
    	if(S->top<0)
    	    return 1;
    	else
    	    return 0; 
    } 
     
    char pop(stack*S)//出栈
    {
    	char e;
    	if(empty(S))
    	{
    		printf("out error!\n");
    		return 0;
    	}
    	else
    	{
    		e=S->data[S->top--];
    		return e;
    	}
    } 


表达式字符串中数字和运算符号均以字符型变量存储,

需要对数字进行识别,以及整型与字符型的转变。以下给出相关功能的函数。


字符型变量是否是数字:

    int isnum(char e)//判断是否为数字 
    {
    	if(e>='0'&&e<='9')
    	    return 1;
    	else
    	    return 0;
    }

字符型转化为整型:

    int num(char e)//字符型转换为整型 
    {
    	int n;
    	n=e-48;//ASCII码 
    	return n;
    } 

整型转化为字符型:

    char nonum(int n)//整型转换为字符型 
    {
    	char e;
    	e=n+48;//ASCII码 
    	return e;
    } 

下面依据题目要求进行主体内容的编码实现。

题目中要求需要对输入的表达式正确性进行必要的判断,

其中括号是否匹配的判断单独进行更为简便。以下给出该功能的函数。

    int correct(char s[])//判断括号是否匹配 
    {
    	stack *S;
    	newstack(S);
    	int flag=1,i=1;
    	while(s[i]!='#'&&flag)
    	{
    		if(s[i]=='(')
    		    push(S,s[i]);
    		if(s[i]==')')
    		    if(pop(S)!='(')
    		        flag=0;
    		i++;
    	}//最先遇到的后括号前必定是与之对应的前括号
         //如若匹配不成功,则flag记为0,即括号不匹配
    	if(!empty(S))
    	    flag=0;
    	return flag;
    } 

本例的第一个重点,也是第一个重要的考察点,

中缀表达式转化为后缀表达式

[转化过程中的要点]

  1. 运算符号优先级的判断
  2. 运算符号遇到括号时的优先级判断

其中,第二条(运算符号遇到括号时的优先级判断)是本例的一个难点,

本例中将在对括号的单独处理中(而不是对运算符或运算符和括号的处理)解决这个问题。

括号的问题将单独考虑,所以在对运算符优先级的判断中暂不考虑,

只对加‘+’、减‘-’、乘‘*’、除‘/’进行判断,以下给出该功能函数。

    int rank(char a,char b)//判断运算符优先级,不包括括号的优先级 
    {
    	if((a=='*'||a=='/')&&(b=='+'||b=='-'))
    	    return 1;
        else
            return 0;
    }


之后是中缀表达式转化为后缀表达式的实现,思路描述如下:

中缀表达式a + b*c + (d * e + f) * g,

其转换成后缀表达式则为a b c * + d e * f + g * +。

转换过程需要用到栈,具体过程如下:

1)如果遇到操作数,我们就直接将其输出

2)如果遇到操作符,则我们将其放入到栈中,遇到左括号时我们也将其放入栈中。

3)如果遇到一个右括号,则将栈元素弹出,将弹出的操作符输出直到遇到左括号为止。

注意,左括号只弹出并不输出。

4)如果遇到任何其他的操作符,如“+”, “*”,“(”等,

从栈中弹出元素直到遇到发现更低优先级的元素(或者栈为空)为止。

弹出完这些元素后,才将遇到的操作符压入到栈中。

有一点需要注意,只有在遇到" ) "的情况下我们才弹出" ( ",其他情况我们都不会弹出" ( "。

5)如果我们读到了输入的末尾,则将栈中所有元素依次弹出。

对该过程仍有疑问的可以阅读以下推荐链接,了解该过程的转化算法思路。

链接1:后缀表达式——凌波ling

链接2:中缀表达式转化为后缀表达式——石锅拌饭


通过以上描述,给出实现以上功能的源码。

    void trans(char s1[],char s2[])//中缀表达式转换为后缀表达式
    {
    	int i=1,j=0;
    	char e;
    	stack *S;
    	newstack(S);
    	while(s1[i]!='#')
    	{
    		if(isnum(s1[i]))//是数字直接输出 
    		    s2[j++]=s1[i];
    		else//运算符号,括号的处理 
    		{
    			if(s1[i]=='(')//前括号直接入栈 
    			    push(S,s1[i]);
    			if(s1[i]==')')//遇到后括号输出栈顶运算符直至遇到左括号,左括号出栈但不输出 
    				while((e=pop(S))!='(')
    					s2[j++]=e;
    			if(s1[i]=='+'||s1[i]=='-'||s1[i]=='*'||s1[i]=='/')
                        //栈空入栈,栈顶为左括号入栈,优先级高于栈顶运算符入栈,
                        //否则出栈并再次判断,实际实现时先进行是否出栈判断 
    			{
    				while(!(empty(S)||S->data[S->top]=='('||rank(s1[i],S->data[S->top])))
    				    s2[j++]=pop(S);
    			    if((empty(S)||S->data[S->top]=='('||rank(s1[i],S->data[S->top])))
    			        push(S,s1[i]);
    			} 
    			if(!(s1[i]=='+'||s1[i]=='-'||s1[i]=='*'||s1[i]=='/'||s1[i]=='('||s1[i]==')'))
    			    printf("非法运算符!\n"); //除过四则运算和括号(+、-、*、/、(、))外,其余判定为非法运算符
    		}
    		i++;
    	}
    	while(!empty(S))//表达式读毕将栈内运算符一一输出 
    	    s2[j++]=pop(S);
    	s2[j]='\0';//字符串结尾 
    }


后缀表达式的求值:如果理解后缀表达式的话,这个步骤应用栈很容易实现。

需要注意的是,是否缺少运算符的判断将在这一步最后进行。

下面给出这部分的子函数。

    int workout(char s2[])//计算后缀表达式的值 
    {
    	int a,b,i=0;
    	stack *S;
    	newstack(S);
    	while(s2[i]!='\0')
    	{
    		if(isnum(s2[i]))//数字入栈等待运算符操作 
    		    push(S,s2[i++]);
    		else
    		{
    			b=num(pop(S)); 
    			a=num(pop(S));//字符型转为整型进行运算 
    		    switch(s2[i++])
    		    {
    		    	case'+':    a=a+b;    push(S,nonum(a));    break;
    		    	case'-':    a=a-b;    push(S,nonum(a));    break;
    		    	case'*':    a=a*b;    push(S,nonum(a));    break;
    		    	case'/':    a=a/b;    push(S,nonum(a));    break;//运算后转为字符型入栈 
    		    	default:    printf("error!");
    			}
    		}
    	}
    	if(S->top!=0)//缺少运算符时,输出提示,但仍然带回栈顶元素的值作为运算结果输出
    	{
    		printf("缺少运算符!得到中间结果!\n");
    	}
    	a=num(pop(S));//字符型转为整形带入返回值 
    	return a;	
    } 

主函数:

    int main()
    {
    	int ans;
    	char s1[80],s2[80];
    	printf("输入表达式:"); 
    	gets(s1);
    	if(!(correct(s1)))
    	{
    		printf("括号不匹配!");
    		return 0;
    	} 
    	else
    	{
    	    trans(s1,s2);
    	    printf("后缀表达式:"); 
    	    puts(s2); 
    	    ans=workout(s2);
    	    printf("ans=%d\n",ans);
    	    system("pause"); 
    	    return 0;
    	}
    }


测试数据的测试

  1. #6+3*(9-7)-8/2#
  2. #(8-2)/(3-1)*(9-6)#
  3. #5+a*(8-4)/2#
  4. #5+(7-3)*6#

#6+3*(9-7)-8/2#



#(8-2)/(3-1)*(9-6)#



#5+a*(8-4)/2#


第三个表达式中有一个运算数为字母'a',在中缀转后缀处理过程中将作为运算符进行判断,

经过判断为“非法运算符”,所以不入栈也不输出在后缀表达式中。

在计算后缀表达式的过程中,因为字符串仍有运算符存在,

但栈中存储的数的数量因为缺少了'a'的位置,所以实际上不够两个,

出栈时就会出错,出现出栈失败时会出现的“out error!”字样。


#5+(7-3)*6#

猜你喜欢

转载自blog.csdn.net/flora715/article/details/81025315