栈的应用----算术表达式计算问题(中缀转后缀,后缀计算)
栈的应用----算术表达式计算问题(中缀转后缀,后缀计算)
问题引入:算术表达式计算是编译系统中的一个基本问题,其实现方法是堆栈的一个典型应用。任何一个算术表达式都是由操作数、运算符和分界符组成的。操作数和运算符是算术表达式的主要部分,分界符标志了一个算术表达式的结束。我们称操作数、运算符、分界符为一个算术表达式的单词。这里为了方便,只设计了加、减、乘、除运算。
算术表达式的计算分为两步:
- 中缀表达式转为后缀表达式
- 后缀表达式的计算。
一、中缀表达式转后缀表达式
1.基本运算规则:
- 先乘除后加减
- 先括号内后括号外
- 同级别先左后右
2.算法如下:
-
设置一个堆栈,初始时将栈顶元素置为"#".
-
顺序读入中缀算术表达式,当读到的但次为操作数时就将其输出,并接着读下一个单词。
-
当读到的单词为运算符时,令x1为当前栈顶运算符的变量,x2为当前扫描读到运算符的变量,把当前读入的单词赋予变量x2,然后比较变量x1的优先级与变量x2的优先级。
-
若x1的优先级高于x2的优先级,则将x1退栈并作为后缀算数表达式的一个输出,然后接着比较新的栈顶运算符x1的优先级和x2的优先级。
-
若x1的优先级低于x2的优先级,则将x2的值进栈,然后接着读下一个单词
-
若x1的优先级等于x2的优先级且x1为"(",x2为")",则将x1退栈,接着读下一个单词。
-
若x1的优先级等于x2的优先级且x1为"#",x2为"#",则算法结束。
3.优先级关系表。
表中Θ1代表栈顶运算符,Θ2代表当前扫描到的运算符。
4.计算过程
二、后缀表达式的计算
1.算法思想:
- 设置一个堆栈存放操作数,从左至右依次扫描后缀算术表达式,没读到一个操作数就将其进栈,没读到一个运算符就从栈顶取出两个操作数施以改运算符所代表的运算操作,并把该运算结果作为一个新的操作数入栈,此过程一直进行到后缀算术表达式读完,最后栈顶的操作数就是改后缀算数表达式的运算结果。
2.计算过程
三、代码实现
头文件:LinkStack.h
typedef char DataType;
typedef struct node
{
DataType data;
struct node *next;
}Stack;
//初始化
void StackInitiate(Stack **head)
{
(*head)=(Stack *)malloc(sizeof(Stack));
(*head)->next = NULL;
}
//判空
int StackNotEmpty(Stack *head)
{
if (head->next!=NULL)
return 1;
else
return 0;
}
//入栈
int StackPush(Stack *head, DataType x)
{
Stack *p;
p = (Stack *)malloc(sizeof(Stack));
p->data = x;
p->next = head->next;
head->next = p;
return 1;
}
//出栈
int StackPop(Stack *head, DataType *x)
{
Stack *p;
p = head->next;
if(p == NULL)
{
printf("堆栈已空无法出栈!");
return 0;
}
else
{
head->next = p->next;
*x=p->data;
free(p);
return 1;
}
}
//取栈顶数据元素
int StackGet(Stack *head, DataType *x)
{
Stack *p;
p = head;
if (p->next == NULL)
{
printf("堆栈已空无法出栈!");
return 0;
}
else
*x = p->next->data;
return 1;
}
//撤销动态申请空间
void Destroy(Stack *head)
{
Stack *p,*p1;
p = head;
while (p != NULL)
{
p1 = p;
p = p->next;
free(p1);
}
}
源文件:
#include<stdio.h>
#include<stdlib.h>
#include"LinStack.h"
#define stkSize 20
#define maxSize 30
int isp(char op) //求位于栈顶操作符的优先级
{
switch (op)
{
case'#':return 0; break;
case'(':return 1; break;
case'*':return 5; break;
case'/':return 5; break;
case'+':return 3; break;
case'-':return 3; break;
case')':return 6; break;
}
}
int icp(char op) //求新读入操作符的优先级
{
switch (op)
{
case'#':return 0; break;
case'(':return 6; break;
case'*':return 4; break;
case'/':return 4; break;
case'+':return 2; break;
case'-':return 2; break;
case')':return 1; break;
}
}
//中缀表达式转为后缀表达式
void mid_to_last(char mid[],char last[])
{
Stack *mystack;
StackInitiate(&mystack);
char ch,ch1,op;
int i = 0,j = 0;
StackPush(mystack,'#');
ch = mid[i++];
while (StackNotEmpty(mystack) && ch != '#')
{
if (ch >= '0'&&ch <= '9')
{
last[j++]=ch;
ch = mid[i++];//输出操作数,读入下一个字符
}
else
{
StackGet(mystack,&ch1);
if (isp(ch1) < icp(ch))
{
StackPush(mystack, ch);//进栈
ch = mid[i++]; //读下一个字符
}
else if (isp(ch1)>icp(ch))
{
StackPop(mystack, &op);
last[j++]=op;
}
else if(isp(ch1)==icp(ch))
{
StackPop(mystack, &op);
ch = mid[i++];
}
}
}
}
//后缀表达式求值
int Calculate(char str[])
{
DataType x,x1,x2;
int i,x3,x4;
Stack *head;
StackInitiate(&head);
for (i=0; str[i] != '\0'; i++)
{
if (str[i] >='0'&&str[i] <='9')//如果读到的是数字,则入栈
{
StackPush(head,str[i]);
}
else //当读到运算符时退栈两个操作数进行该操作符的计算
{ //退栈出来的数据类型书char型,运算时将其转换成int型,运算的结果再转换为char型入栈
StackPop(head, &x2);
StackPop(head, &x1);
x3 = int(x2 - 48);
x4 = int(x1 - 48);
switch (str[i])
{
case'+':
{
x4 = x4+x3;
break;
}
case'-':
{
x4 = x4-x3;
break;
}
case'*':
{
x4 = x4*x3;
break;
}
case'/':
{
if (x3==0)
{
printf("分母为零错误!");
exit(0);
}
else
{
x4 = x4/x3;
break;
}
}
}
x1 = char(x4 + 48); //计算的结果转为字符型
StackPush(head,x1); //计算结果入栈
}
}
StackPop(head,&x); //得到计算结果,存入x中
x = int(x - 48); //将字符型计算结果转为int型
return x;
}
int main()
{
int x;
char data[stkSize];
printf("请输入要转换的中缀表达式:");
scanf("%s",data);
char last[maxSize]="";
mid_to_last(data, last); //调用中缀转后缀函数
x=Calculate(last); //后缀表达式的计算
printf("后缀表达式为:%s\n", last);
printf("后缀表达式的计算结果为:%d\n", x);
return 0;
}
四、运行结果(就用上面的2+(3-4*5)测试)
可以看到,上述的分析过程和结果都正确,其实熟悉编译原理的同学可以直接用“移进”和“规约动作”实现中缀算数表达式到后缀算数表达式的转换。
上一篇: Java中缀表达式转后缀表达式并计算
下一篇: 修改windows文件的换行符