表达式求值(后缀表达式)--栈

本文深入探讨了计算机中表达式求值的不同标识方法,包括前缀、中缀和后缀表达式,并详细阐述了后缀式最适合表达式运算的特点。通过实例演示了如何使用栈来实现后缀式求值过程。此外,文章还介绍了如何从原始中缀表达式转换为后缀表达式的具体方法,涉及符号优先级与操作逻辑。

数据结构 表达式求值




在计算机中,表达式可以有三中不同的标识方法

设 Exp = S1 + OP + S2

则,
称 op + S1 + S2 为表达式的 “前缀表达式”

称 S1 + OP + S2 为表达式的 “中缀表达式”

称 S1 + S2 + OP 为表达式的 “后缀表示法”

可见,它以运算符所在不同位置命名的。


例如: Exp = a * b + (c - d/e)*f

前缀式: + * a b * - c / d e f

中缀式: a * b + c - d / e * f

后缀式: a b * c d e / - f * +


结论:
2)运算符的相对次序不同;
3)中缀式丢去了括弧信息,致使运算次序不正确;
4)前缀式的运算规则为:
      连续出现的两个操作数和在它们之前且紧靠它们的运算符构成一个最小表达式;
5)后缀式的运算规则为:
      运算符在式中出现的顺序恰为表达式的运算顺序;
      每个运算符和在它之前出现且紧靠它的两个操作数构成一个最小表
式;


由上面可知,后缀式最适合表达式运算的,实际上大多数编译器上用的也是后缀式。

那么如何用后缀式求值?

后缀式操作的规则:

先找运算符,再找操作数。


也就是碰到操作数,保留,碰到运算符,就运算,运算后结果保留在前操作数位置。

根据操作的规则,自然能想到用栈来存取。



下面来演示下上面的例子:

例: Exp = a * b + (c - d/e)*f;

后缀式: a b * c d e / - f * +;


入栈顺序:a b * c d e / - f * +;

1.a入栈,b入栈;
2.遇到“*”操作符,运算“a*b”表达式,保留在a(前操作数)的位置;
3.c,d,e,依次进栈;
4.遇到“/”操作符,执行“d/e”运算(最近的两个操作数),存入d的位置;
5.遇到“-”操作符,将c与d位置的值(设为x)执行“c-x”运算,存入c,(设为y);
6.f入栈;
7.遇到“*”操作符,执行“y*f”运算(设为z),存入y的位置;
8.遇到“+”操作符,执行“a+z”运算,存入a位置;
9.运算结束,退栈,输出结果;

OK,如上现在解决了如行用后缀式求值,One more thing :如何得到后缀式表达式。



遇到问题我们就要解决。

问:如何从 原表达式 求得 后缀式 ?

分析:我们看下“原表达式”跟“后缀式”的运算符变化规则:


原表达式: a + b * c - d / e * f ;

后缀式: a b c * d e / f * - ;

方法一:符号都是有各自的优先级
(1)首先,需要分配2个栈,栈s1用于临时存储运算符(含一个结束符号),此运算符在栈内遵循越往栈顶优先级越高的原则;栈s2用于输入逆波兰式,为方便起见,栈s1需先放入一个优先级最低的运算符,在这里假定为'#';

(2)从中缀式的左端开始逐个读取字符x,逐序进行如下步骤

  1.若x是操作数,则分析出完整的运算数(在这里为方便,用字母代替数字),将x直接压入栈s2;

  2.若x是运算符,则分情况讨论:

    若x是'(',则直接压入栈s1;

    若x是')',则将距离栈s1栈顶的最近的'('之间的运算符,逐个出栈,依次压入栈s2,此时抛弃'(';

    若x是除'('和')'外的运算符,则再分如下情况讨论:

      若当前栈s1的栈顶元素为'(',则将x直接压入栈s1;

      若当前栈s1的栈顶元素不为'(',则将x与栈s1的栈顶元素比较,若x的优先级大于栈s1栈顶运算符优先级,则将x直接压入栈s1。否者,将栈s1的栈顶运算符弹出,压入栈s2中,直到栈s1的栈顶运算符优先级别低于(不包括等于)x的优先级,或栈s2的栈顶运算符为'(',此时再则将x压入栈s1;


(3)在进行完(2)后,检查栈s1是否为空,若不为空,则将栈中元素依次弹出并压入栈s2中(不包括'#');      

(4)完成上述步骤后,栈s2便为逆波兰式输出结果。但是栈s2应做一下逆序处理,因为此时表达式的首字符位于栈底;

C代码
 char *RPExpression(char *e)
 /* 返回表达式e的逆波兰式 */
 {
     //栈s1用于存放运算符,栈s2用于存放逆波兰式
     Stack s1,s2;
     InitStack(s1);
     InitStack(s2);    
     
     //假设字符'#'是运算级别最低的运算符,并压入栈s1中
     Push(s1,'#');
     
     //p指针用于遍历传入的字符串,ch用于临时存放字符,length用于计算字符串长度 
     char *p=e,ch;
     int length=0;
     for(;*p!='\0';p++)//逐个字符访问
     {
         switch(*p)
         {
             //遇'('则直接入栈s1
             case '(':
                 Push(s1,*p);
                 break;
             //遇')'则将距离栈s1栈顶的最近的'('之间的运算符,逐个出栈,依次送入栈s2,此时抛弃'('
             case ')':
                 while(Top(s1)!='(')
                 {
                     Pop(s1,ch);
                     Push(s2,ch);
                 }
                 Pop(s1,ch);
                 break;
             //遇下列运算符,则分情况讨论:
             //1.若当前栈s1的栈顶元素是'(',则当前运算符直接压入栈s1;
             //2.否则,将当前运算符与栈s1的栈顶元素比较,若优先级较栈顶元素大,则直接压入栈s1中,
             //  否则将s1栈顶元素弹出,并压入栈s2中,直到栈顶运算符的优先级别低于当前运算符,然后再将当前运算符压入栈s1中
             case '+':
             case '-':
                 for(ch=Top(s1);ch!='#';ch=Top(s1))
                 {                
                     if(ch=='(')
                     {                        
                         break;
                     }
                     else
                     {
                         Pop(s1,ch);
                         Push(s2,ch);                        
                     }                                          
                 }
                 Push(s1,*p);
                 length++;
                 break;
             case '*':
             case '/':
                 for(ch=Top(s1);ch!='#'&&ch!='+'&&ch!='-';ch=Top(s1))
                 {                
                     if(ch=='(')
                     {                        
                         break;
                     }
                     else
                     {
                         Pop(s1,ch);
                         Push(s2,ch);
                     }                                          
                 }
                 Push(s1,*p);
                 length++;
                 break;
             //遇操作数则直接压入栈s2中
             default:
                 Push(s2,*p);   
                 length++;             
         }
     }    
     //若栈s1非空,则将栈中元素依次弹出并压入栈s2中
      while(!StackEmpty(s1)&&Top(s1)!='#')
     {
         Pop(s1,ch);
         Push(s2,ch);        
     }
     //最后将栈s2输出,逆序排列成字符串;
     char *result;
     result=(char *)malloc(sizeof(char)*(length+1));
     result+=length;
     *result='\0';
     result--;
     for(;!StackEmpty(s2);result--)
     {
         Pop(s2,ch);
         *result=ch;        
     }
     ++result;
     return result;
 }


 

 

方法二:符号分成栈内和栈外优先级

#include "arithmetic.h"
void Arithmetic::midTopost(char *c)
{
	int i = 0;
	int k;
	int j;
	char tmp;
	char a[100];
	char num[10];

	//下面开始把字符串中的操作符(+-*/)放入后缀表达式
	cStack.makeEmpty();
	cStack.push('#');
	//字符串最后都加一个#
	strcat(c, "#");
	k = 0;
	while (c[k] != '/0')
	{
		if (c[k] == '+' || c[k] == '-' || c[k] == '*' || c[k] == '/' || c[k] == '(')
		{
			//如果优先级低,一直退栈,否则入栈
			while (isp(cStack.getTop()) > icp(c[k]))
			{
				post[i++].op = cStack.pop();
			}
		    cStack.push(c[k]);
		}
		else if (c[k] == ')')
		{
			//一直退栈到'('
			while (isp(cStack.getTop()) >= icp(')'))
			{
				tmp = cStack.pop();
				if (tmp != '(')
				{
				    post[i++].op = tmp;
				}
			}
			
		}
		else if (c[k] == '#')
		{
			while (isp(cStack.getTop()) > icp('#'))
			{
				tmp = cStack.pop();
				post[i++].op = tmp;
			}
		}
		else if (c[k] >= '0' && c[k] <= '9')
		{
			//数字,继续往后获取字符,如果是数字,全部拼装到num中,然后转换成double类型
			j = 0;
			memset(num, 0, 10);
			num[j] = c[k];
			k = k + 1;
			while (c[k] >= '0' && c[k] <= '9')
			{
				num[++j] = c[k];
				k++;
			}
			post[i].num = atof(num);
	    	post[i].op = '#';
			i++;
			k--;
		}
		else
		{
			//其他非法字符,不处理
		}
		k++;
	}
	curLen = i;
}
//栈内优先级,栈内优先级大于栈外优先级
//'('的栈内优先级应该比所有的操作符都小(除了#)
//')'的栈内优先级应该最大
//+-的优先级一致,*/的优先级一致
int Arithmetic::isp(char op)
{
	int pri;
	switch (op)
	{
	case '#' : pri = 0;
		break;
	case '(' : pri = 1;
		break;
	case '+' : pri = 3;
		break;
	case '-' : pri = 3;
		break;
	case '*' : pri = 5;
		break;
	case '/' : pri = 5;
		break;
	case ')' : pri = 10;
		break;
	}
	return pri;
}
//栈外优先级
int Arithmetic::icp(char op)
{
	int pri;
	switch (op)
	{
	case '#' : pri = 0;
		break;
	case '(' : pri = 10;
		break;
	case '+' : pri = 2;
		break;
	case '-' : pri = 2;
		break;
	case '*' : pri = 4;
		break;
	case '/' : pri = 4;
		break;
	case ')' : pri = 1;
		break;
	}
	return pri;
}
//根据后缀表达式进行四则运算
double Arithmetic::calculate()
{
	int i;
	double result = 0;
	double left, right;
	for (i = 0; i < curLen; i++)
	{
		//如果是数字就入栈
		if (post[i].op == '#')
		{
			dStack.push(post[i].num);
		}
		else //如果是操作符,就取出两个数字进行运算
		{
			right = dStack.pop();
            left = dStack.pop();
			switch (post[i].op)
			{
			case '+' : dStack.push(left + right);
				break;
			case '-' : dStack.push(left - right);
				break;
			case '*' : dStack.push(left * right);
				break;
			case '/' : dStack.push(left / right);
				break;
			}
		}
	}
	return dStack.pop();
}






综上所述,得到规律:2个栈操作数字栈(后缀式),运算符栈

1)设立操作数栈;
2)设表达式的结束符为“#”,予设运算符栈的栈底为“#”;
3)若当前字符是操作数,则直接发给后缀式;
4)若当前运算符的优先级高于栈顶运算符,则进运算符栈;
5)否则,退出栈顶运算符发送给后缀式;
6)“(”对它前后的运算符起隔离作用(执行进栈操作),“)”可视为自相应左括弧开始的表达式结束。

 


在得到后缀式后,怎么处理:

1,在操作数栈中,直接处理一旦一个操作符进了操作数栈,直接将此操作符的前两个操作数,做成表达式得出结果,在栈中存在那个操作符的位置(因为操作符一入栈就在栈顶,所以也就是把结果放在了栈顶),2个操作数和操作符消失,循环往复。

2,直接由操作数栈得到一个后缀表达式,此时应该将此栈表达成一个链表,从头遍历链表,一旦遇到一个操作符内容就将此位置的前2个内容取出得到表达式算结果,放入当前操作符的位置,同时将前2个节点删除。


伪代码如下:[code]void transform(char suffix[], char exp[])
{
InitStack(s);
Push(S,'#'); //入栈

p = exp;
ch = *p;


while ( !StackEmpty( S ) ) //判断输入
{
if ( !IN( ch , OP ) ) Pass(Suffix,ch);//不是操作符则为操作数
else {
seitch( ch ) //判断操作符
{
case "(" : Push (S,ch); break;
case ")" : {……}
……

}
}
if(ch!='#'){ p ++; ch = *p;} //遇到#退出
}
}[/code]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值