#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define MAX 100
#define Error 0
#define True 1
typedef float Num;//为防止以后变换操作数类型需要
typedef struct
{
Num data[MAX];
int top;
}StackNum;//运算数栈
typedef struct
{
char data[MAX];
int top;
}StackChar;//运算符栈
void InitNum(StackNum* p)
{
p->top = 0;
}
void InitChar(StackChar* p)
{
p->top = 0;
}
void PushNum(StackNum* p, Num e)
{
if (p->top == MAX)
printf("运算数栈满!\n");
else
{
p->data[p->top] = e;
p->top++;
}
}
void PushChar(StackChar* p, char e)
{
if (p->top == MAX)
printf("运算符栈满!\n");
else
{
p->data[p->top] = e;
p->top++;
}
}
void PopNum(StackNum* p, Num* e)
{
if (p->top == 0)
printf("运算数栈空!\n");
else
{
p->top--;
*e = p->data[p->top];
}
}
void PopChar(StackChar* p, char* e)
{
if (p->top == 0)
printf("运算符栈空!\n");
else
{
p->top--;
*e = p->data[p->top];
}
}
void Fun(StackNum* p, char e)
{
Num temp1, temp2;//存放两个临时操作数
PopNum(p, &temp2);
PopNum(p, &temp1);
switch (e)
{
case '+':PushNum(p, temp1 + temp2); break;
case '-':PushNum(p, temp1 - temp2); break;
case '*':PushNum(p, temp1 * temp2); break;
case '/':PushNum(p, temp1 / temp2); break;
}
}
Num GetNum(StackNum p)
{
return p.data[p.top - 1];
}
void main()
{
int i;//循环变量
Num temp;//存放一个临时转换数
char str[MAX], ch;//存放中缀表达式原式,临时运算符
//-----------
StackNum n1;
StackChar c1;
InitNum(&n1);
InitChar(&c1);
//------------
for (;;)
{
int j;//判断变量
j = 1;
printf("请输入中缀表达式:");
gets(str);
/*
注意字符串输入函数与scanf("%s",str) 的区别,scanf遇到空白字符,
包括空格,制表符,换行符时均会停止输入,所以不可取,而gets功能为读入一行,
并将换行符转换为字符串结束符。
*/
for (i = 0;str[i] != '\0'; i++)//读完整字符串-----字符串结束标志'\0'
{
if (str[i] >= '0' && str[i] <= '9')//分岔点一:如果为数字
{
temp = str[i] - '0';//-----将字符转换为数值
while (str[i + 1] != '\0')//多位数值获取
{
if (str[i + 1] >= '0' && str[i + 1] <= '9')
{
temp = temp * 10 + str[i + 1] - '0';//------注意!
i++;
}
else
break;//如果不是多位数字,则跳出多位获取循环
}
PushNum(&n1, temp);//将获取来的数值入栈
}
else if (str[i] == '+' || str[i] == '-' || str[i] == '*' || str[i] == '/' || str[i] == '(' || str[i] == ')')//分岔点二:如果为运算符
{
switch (str[i])//表达式可为:整型/字符型/枚举型-----C语言中
{
//case 后可为 整型,字符型----C语言中
case '+':
if (c1.data[c1.top - 1] != '+' && c1.data[c1.top - 1] != '-' && c1.data[c1.top - 1] != '*' && c1.data[c1.top - 1] != '/')
{
PushChar(&c1, '+');
}
else//如果不然,则将之前的先都出栈并计算,然后再入栈
{
while (c1.top > 0 && c1.data[c1.top - 1] != '(')//将优先级高的运算符先输出计算,其中括号内的优先级最高
{
PopChar(&c1, &ch);
Fun(&n1, ch);//计算,并压运算数栈
}
PushChar(&c1, '+');
}
; break;
case '-':
if (c1.data[c1.top - 1] != '+' && c1.data[c1.top - 1] != '-' && c1.data[c1.top - 1] != '*' && c1.data[c1.top - 1] != '/')
{
PushChar(&c1, '-');
}
else//如果不然,则将之前的先都出栈并计算,然后再入栈
{
while (c1.top > 0 && c1.data[c1.top - 1] != '(')//将优先级高的运算符先输出计算,其中括号内的优先级最高
{
PopChar(&c1, &ch);
Fun(&n1, ch);//计算,并压运算数栈
}
PushChar(&c1, '-');
}
; break;
case '*':
if (c1.data[c1.top - 1] != '*' && c1.data[c1.top - 1] != '/')
{
PushChar(&c1, '*');
}
else//如果不然,则将之前的先都出栈并计算,然后再入栈
{
while (c1.top > 0 && c1.data[c1.top - 1] != '(')//将优先级高的运算符先输出计算,其中括号内的优先级最高
{
PopChar(&c1, &ch);
Fun(&n1, ch);//计算,并压运算数栈
}
PushChar(&c1, '*');
}
; break;
case '/':
if (c1.data[c1.top - 1] != '*' && c1.data[c1.top - 1] != '/')
{
PushChar(&c1, '/');
}
else//如果不然,则将之前的先都出栈并计算,然后再入栈
{
while (c1.top > 0 && c1.data[c1.top - 1] != '(')//将优先级高的运算符先输出计算,其中括号内的优先级最高
{
PopChar(&c1, &ch);
Fun(&n1, ch);//计算,并压运算数栈
}
PushChar(&c1, '/');
}
; break;
case '(':
PushChar(&c1, '(');
; break;
case ')'://并没有将')'压入栈中,只是当作一种出栈信号
while (c1.data[c1.top - 1] != '(')
{
PopChar(&c1, &ch);
Fun(&n1, ch);//计算,并压运算数栈
}
PopChar(&c1, &ch);//将'('也出栈,但并不计算
; break;
}
}
else//其余输入判定为错误
{
printf("输入错误\n");
j = 0;
break;
}
}
while (c1.top > 0)//将剩余的运算符出栈并计算
{
PopChar(&c1, &ch);
Fun(&n1, ch);
}
if (j == 1)
{
printf("\t\t%s=%.2f", str, GetNum(n1));
printf("\n");
}
else {
printf("\n");
}
system("pause");
}
}
通过栈的各种基本操作求值中缀表达式,编写实现栈基本操作的程序,将中缀表达式化为后缀表达式,进而求出中缀表达式的值。
中缀表达式是一个通用的算法或逻辑表达式,形如(6+8)*9-1,较易被人类大脑理解并使用,是我们常用的算式表达方式。运算法则即为“先算乘除,后算加减,有括号先算括号里的”。而对于计算机来说,中缀表达式无法被识别,编程者需要通过程序代码的编写,将表达式转化分解为后缀表达式进行计算。所谓后缀表达式,即为运算数在前,运算符在后,有别于人类思维结构较易理解的中缀表达式,这种表达式更易被计算机语言理解。以该中缀表达式的转换为例可以直观看出后缀表达式的特点。
(a+b)*c-(a+b)/e的后缀表达式为:
(a+b)*c-(a+b)/e
→((a+b)*c)((a+b)/e)-
→((a+b)c*)((a+b)e/)-
→(ab+c*)(ab+e/)-
→ab+c*ab+e/-
后缀表达式运算符优先级:{ (, ) } > { *, / } > { +, - }。注意,同级运算中,先出现的(左侧)优先级高。
那我们在电脑上为什么可以按照我们的想法去运算中缀形式的运算式呢?原因在于计算机程序设计者为了方便我们使用,在电脑上已经预先封装好了相应的运算程序,而我们要做的就是将这个封装的程序进行再现。
程序实现主要依靠栈结构。需要创建数字栈和运算符栈两个栈,并以运算符栈作为程序进行控制的主导,比较压入栈中运算符的优先级,如果栈顶运算符优先级高于栈内其他运算符,就弹出数字进行相应计算,并将运算结果压入栈,继续运行。遇到“)”就将与“(”之间的符号及相应数字弹出运算,再将结果压入。核心概括来说就是利用栈结构,将中缀表达式分解为后缀表达式的分部形式进行运算。
(2)实现原理
Ⅰ 建立两个栈,其中左边的栈,暂存操作数(记为S1);右边的栈,暂存运算符(记为S2)。从左到右,扫描中缀表达式。
Ⅱ 遇到操作数,则入S1栈;遇到左括号,则入S2栈;如遇到运算符,则准备入S2栈。入栈前须进行如下判断:
①若S2栈为空或S2栈的栈顶为左括号,则运算符可直接入S2栈。
②若当前运算符的优先级大于栈顶运算符,则可如S2栈。
③若当前运算符的优先级小于等于栈顶运算符,则现将栈顶运算符出栈,直到当前运算符的优先级大于栈顶运算符,即可如S2栈。
④若运算符为右括号,则将S2中从栈顶到左括号的所有运算符均出栈。
总结:S2栈每出栈一个运算符,则S1栈出栈两个操作数进行一次运算,其中先出栈的操作数位于运算符右边,后出栈的运算符位于运算符左边,计算出结果并入栈S1。当整个中缀表达式全部扫描完后,S2栈中依旧有运算符,则将所有运算符出栈,最后在S1栈顶的数即为整个表达式的求值结果。
(3)实验步骤
- 首先编写有关栈的各种操作的程序
操作数栈和运算符栈的初始化:
void InitNum(StackNum *p)
{
p->top = 0;
}
运算符和操作数入栈:
void PushNum(StackNum *p, Num e)
{
if (p->top == MAX)
printf("运算数栈满!\n");
else
{
p->data[p->top] = e;
p->top++;
}
}
运算符和操作数出栈:
void PopNum(StackNum *p, Num *e)
{
if (p->top == 0)
printf("运算数栈空!\n");
else
{
p->top--;
*e = p->data[p->top];
}
}计算并压入运算数栈:
void Fun(StackNum *p, char e)
{
Num temp1, temp2;//存放两个临时操作数
PopNum(p, &temp2);
PopNum(p, &temp1);
switch (e)
{
case '+':PushNum(p, temp1 + temp2); break;
case '-':PushNum(p, temp1 - temp2); break;
case '*':PushNum(p, temp1*temp2); break;
case '/':PushNum(p, temp1 / temp2); break;
}
}取栈顶元素:
Num GetNum(StackNum p)
{
return p.data[p.top - 1];
}
- 根据上述中缀表达式转换为后缀表达式的转换原理编写主程序
伪代码实现如下:
for(遍历所有字符){
if(数字) 输出;
else{
if(左括号) 入栈;
else if(右括号) 弹栈,一直弹到遇到一个左括号;
else{
判断操作符优先级;
if(栈顶优先级小于当前优先级){
压栈;
}else{
将小于当前优先级的所有操作符出栈,然后入栈;
}
}
}
}
- 带入各种可能的测试数据进行测试,验证程序的运行情况、完整性和鲁棒性
在中缀表达式求值的具体实现中,我们定义了两个栈,分别是StackNum和StackChar,他们分别用来存储运算数和运算符,利用栈后进先出的特点进行计算。在构造的过程中,为了使代码更简洁可读,借助了C语言中的数组,本质上是运用了栈的顺序实现。使用运算数栈和运算符栈这两个栈实现中缀向后缀的转化并完成计算。
为了实现模块化编程,增加代码可读性,我们定义了一些函数。其中InitNum和InitChar分别用于运算数栈和运算符栈初始化,PushNum和PushChar分别用于将运算数压入栈和运算符压入栈,PopNum和PopChar分别用于将运算数和运算符压出栈。GetNum用于取栈顶元素,Fun用于计算并将结果压入运算数栈。
在主函数中,我们实现了中缀表达式向后缀表达式转换的过程,并完成了后缀表达式的计算,最终实现的是一个交互式的可持续输入中缀表达式并求值的程序,此外,我们的程序还能够处理各种输入错误。
主函数中,我们通过gets函数获得用户的中缀表达式输入。通过for循环我们逐个读取直到读完整个字符串,对每个字符进行如下处理。如果是运算数字符,我们将其转化为运算数,并且通过while循环可以实现对多位数值的获取,然后将单位运算数或多位运算数压入运算数栈。如果是运算符字符,通过switch函数分别对“+”“-”“*”“/”“(”“)”进行相应处理,此部分是本程序的核心代码部分,完成了中缀表达式向后缀表达式转化的过程和部分计算。以下是对“+”的处理,只有在空栈或最上面为“(”的时候,“+”才被压入,其他情况一概把运算符输出计算,直到全部出栈或遇到“(”才停止输出,才把“+”压入栈中。
实质上,是利用不同运算符的优先级进行处理。当运算符优先级大于栈顶运算符时,把它直接压入栈,当运算符的优先级小于或等于栈顶运算时,将栈顶运算符弹出并输出,再比较新的栈顶运算符,直到该运算符大于栈顶运算符优先级为止,然后将该运算符压入栈中。对于左括号和右括号,将其看成特殊的运算符,左括号优先级最高直接压入运算符栈,当遇到右括号时,将栈顶的运算符弹出并输出,直到遇到左括号,将其出栈但不输出,这样处理可以实现对中缀表达式括号内算式的计算。当所有对象处理完以后,把运算符栈中剩余的运算符出栈输出完成最终计算,得出最终的结果。代码最后完成打印完整的中缀表达式求值算式。