C语言编程对一个逆波兰式进行求值,算式与逆波兰式

致憨憨的从前

当年,老师布置一道作业:编写一个计算器,要求输入算式,给出结果。算式中只包含+-*/^这几个运算符,算式中不含负数。由于是Python课程,我很快给出了解题方式,如下:

while True:

input_str = input("输入表达式:")

print(eval(input_str))

不出意料,该程序完美地解决了问题,唯一的缺点是老师看到代码的时候差点给我挂科了。

于是我重新编写代码试图解决这个问题。对于当时啥都不会的我来说,这是一个难题,我的解决思路如下:

从头开始检索字符串,直到找到第一个)。

从找到的)开始往回检索,直到找到第一个(。显而易见,这两个坐标中间是不含括号,只含数字和运算符。

提取两个坐标之间的字符串,根据运算优先级循环检索算式内的运算符并进行计算,并将计算结果字符串中的操作数和操作符,直到该算式只剩下一个数字,这个数字就是子式的运算结果。

将该数字替换回原算式的两个括号左边中间的位置,返回步骤1并重复,直到算式内不含括号。

执行步骤3,剩下的数字则为最终结果。

这个解决思路从理论上来说是没有问题的,但是存在以下问题:

需要进行很多次循环,对于太长的算式会消耗太多时间

计算过程中对算式字符串进行多次修改,对于太长的算式会消耗太多时间

不过,尽管存在问题,但是对于一个课程作业来说,还是没有太大影响的,所以我也一直没有想是否有更好的解决方法。但是,前两天我刷题的时候,又遇到了一次相同的题目,而且是要求用c语言写。对于没有那么熟练使用c语言的我来说,字符串的处理是比较困难的,所以我在网上寻找题目的解决方法,从而了解到逆波兰式这个神奇的东西。这也警醒我,对于一个问题,即使自己有想法能够解决问题,也一定要交流学习是否有更好的解决方法,更新自己的知识库。

为什么需要逆波兰式

首先介绍下逆波兰式的历史(摘自维基百科-逆波兰表示法):

逆波兰表示法(Reverse Polish notation,RPN,或逆波兰记法),是一种是由波兰数学家扬·武卡谢维奇1920年引入的数学表达式方式,在逆波兰记法中,所有操作符置于操作数的后面,因此也被称为后缀表示法。逆波兰记法不需要括号来标识操作符的优先级。

逆波兰结构由弗里德里希·鲍尔(Friedrich L. Bauer)和艾兹格·迪科斯彻在1960年代早期提议用于表达式求值,以利用堆栈结构减少计算机内存访问。逆波兰记法和相应的算法由澳大利亚哲学家、计算机学家查尔斯·汉布尔(Charles Hamblin)在1960年代中期扩充[1][2]

在1960和1970年代,逆波兰记法广泛地被用于台式计算器,因此也在普通公众(如工程、商业和金融等领域)中使用。

中缀表达式在计算机的计算过程

中缀表达式的意思是,运算符处于两个运算对象之间,例如:1+2,1和2是运算对象,+是运算符,处于中间。

对于给定算式1+(2+3)*(4^5+6/3),我们使用一棵树来直观感受该中缀表达式算式结构。

graph TD

A((+))

A --- B((1))

A --- C((*))

C --- D((+))

D --- E((2))

D --- F((3))

C --- G((+))

G --- H((^))

H --- I((4))

H --- J((5))

G --- K((/))

K --- L((6))

K --- M((3))

显而易见,对该树进行中序遍历,可以得到原来的中缀表达式。对于这棵树中任意操作符,我们可以这样计算该子式的结果:

取出左子树的操作数

取出该操作符

取出右子树的操作数

根据操作符对操作数进行计算

由此,一层一层地剪掉子树,将子树的计算结果替换到操作符的节点位置,如此重复,直到最后只留一个根节点,则根节点的数字就是该式的计算结果。实际上,我上文提到的完成课程作业的方法跟这里的解决思想是一摸一样的,只不过这里使用了树的节点作为表达,而我是直接替换了字符串的相应位置。

中缀表达式的缺陷

从上一节可以看出,使用中缀表达式进行计算,需要先构建出树,再递归计算子树的结果,这样就会导致当运算式很长的情况下,这棵树会非常深,与之对应的,递归计算结果的时候就会导致递归堆栈非常深。并且,使用算式字符串得出该树也不是一件简单的事情,需要考虑运算符优先级的问题。

将中缀表达式转为逆波兰式

将中缀表达式的树转为逆波兰式

同样是上述的算式1+(2+3)*(4^5+6/3),其树结构同样如下:

graph TD

A((+))

A --- B((1))

A --- C((*))

C --- D((+))

D --- E((2))

D --- F((3))

C --- G((+))

G --- H((^))

H --- I((4))

H --- J((5))

G --- K((/))

K --- L((6))

K --- M((3))

接下来,对其进行后序遍历,即:

取出左子树的操作数

取出右子树的操作数

取出该操作符

根据操作符对操作数进行计算

可以得到,该树的后序遍历结果是:

1 2 3 + 4 5 ^ 6 3 / + * +

该式就是算式1+(2+3)*(4^5+6/3)对应的逆波兰式。

不构建树得到逆波兰式的方法

虽然我们知道了如何将中缀表达式对应的树转换为逆波兰式,由于是后序遍历,计算该式的结果非常容易。但是相较于中序遍历,这种将树进行后序遍历的做法并没有解决任何问题。不信?我将中缀表达式的缺陷粘贴下来,可以看到毫无违和感,因为他们本质上是一样的,不过是换了一种形式表达而已。

中缀表达式的缺陷

从上一节可以看出,使用中缀表达式进行计算,需要先构建出树,再递归计算子树的结果,这样就会导致当运算式很长的情况下,这棵树会非常深,与之对应的,递归计算结果的时候就会导致递归堆栈非常深。并且,使用算式字符串得出该树也不是一件简单的事情,需要考虑运算符优先级的问题。

那应该如何在不构建树的情况下得到逆波兰式呢?我们用一个栈来存储逆波兰式,称为结果栈,再用一个辅助栈来解决运算优先级的问题(原理是用辅助栈存储低优先级的运算符,直到碰到高优先级运算符再按顺序压入到结果栈)。直接将算式转换为逆波兰式的运行方法如下:

从左到右遍历算式,逐个取出里面的元素。

如果取出的是操作数,直接压入结果栈

如果取出的是左括号(,压入辅助栈

如果取出的是运算符,如果此时辅助栈为空,或者辅助栈栈顶为(,或者如果该运算符优先级大于辅助栈顶部的运算符,则将该运算符压入辅助栈。否则,弹出辅助栈栈顶的运算符并压入到结果栈中,直到辅助栈栈顶的运算符低于该运算符,则将该运算符压入辅助栈

如果取出的是右括号),则将逐个弹出辅助栈中的运算符并压入到结果栈中,直到辅助栈栈顶为(,弹出(并抛弃,将)也抛弃

重复以上步骤,直到处理完整个算式

将辅助栈中所有运算符逐个弹出并压入到结果栈中

将结果栈进行逆序处理(因为栈的输出本来就是逆序的,逆逆得正)

使用逆波兰式计算最终结果

创建一个操作数栈,计算方法如下(注意此时结果栈已进行逆序处理):

不断弹出结果栈并压入到操作数栈,直到结果栈栈顶为运算符

弹出操作数栈顶为v2,再弹出操作数栈顶为v1,将[v1操作符v2,如v1+v2]的结果压入操作数栈,弹出结果栈栈顶的运算符并抛弃

重复以上步骤,直到结果栈为空,则此时操作数栈只剩一个数,为最终结果

C语言编写对应程序

# include

# include

// 算式的最长长度

# define MAX_LEN 50

int calc(int v1, char opera, int v2) {

int res;

switch (opera) {

case '+':

return v1+v2;

case '-':

return v1-v2;

case '*':

return v1*v2;

case '/':

return v1/v2;

case '^':

res = 1;

for (int i=0; i

res *= v1;

}

return res;

}

}

int char_type(char c) {

if (c == '^') return 3;

if (c == '*' || c == '/') return 2;

if (c == '+' || c == '-') return 1;

if (c>=48 && c<=57) return 0;

return -1;

}

int main(void) {

char str[MAX_LEN];

scanf("%s", str);

int len = strlen(str);

char stack1[MAX_LEN] = ""; // 辅助栈

char stack2[MAX_LEN] = ""; // 结果栈

int stack1_top = 0;

int stack2_top = 0;

for (int i=0; i

if (char_type(str[i]) == 0) {

while (char_type(str[i])==0 && i

stack2[stack2_top++] = str[i++];

}

stack2[stack2_top++] = ' ';

}

if (str[i] == '(') {

stack1[stack1_top++] = str[i];

}

if (char_type(str[i]) > 0) {

if (stack1_top==0 || stack1[stack1_top-1]=='(' || char_type(str[i])>char_type(stack1[stack1_top-1])) {

stack1[stack1_top++] = str[i];

} else {

while (char_type(stack1[stack1_top-1]) >= char_type(str[i])) {

stack2[stack2_top++] = stack1[--stack1_top];

}

stack1[stack1_top++] = str[i];

}

}

if (str[i] == ')') {

while (stack1[--stack1_top] != '(') {

stack2[stack2_top++] = stack1[stack1_top];

}

stack1[stack1_top] = '\0';

}

}

while (stack1_top) {

stack2[stack2_top++] = stack1[--stack1_top];

}

printf("逆波兰式:%s\n", stack2);

int calc_stack[MAX_LEN]; // 操作数栈

int calc_stack_top=0;

char temp[MAX_LEN]; // 用于char*到int的转换

int temp_index = 0;

memset(temp, 0, MAX_LEN);

for (int i=0; i

if (char_type(stack2[i]) == 0) {

temp[temp_index++] = stack2[i];

} else if (stack2[i] == ' ') {

calc_stack[calc_stack_top++] = atoi(temp);

temp_index = 0;

memset(temp, 0, MAX_LEN);

} else {

int v2 = calc_stack[--calc_stack_top];

int v1 = calc_stack[--calc_stack_top];

calc_stack[calc_stack_top++] = calc(v1, stack2[i], v2);

}

}

printf("结果:%d\n", calc_stack[0]);

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个简单的逆波兰表达式求值的 C 语言程序。假设输入的逆波兰表达式已经存储在一个字符数组中,程序使用栈来实现求值。 ```c #include <stdio.h> #include <stdlib.h> #include <ctype.h> #define MAX_STACK_SIZE 100 int stack[MAX_STACK_SIZE]; int top = -1; int is_operator(char c) { return (c == '+' || c == '-' || c == '*' || c == '/'); } void push(int n) { if (top >= MAX_STACK_SIZE - 1) { fprintf(stderr, "Error: Stack overflow\n"); exit(EXIT_FAILURE); } stack[++top] = n; } int pop() { if (top < 0) { fprintf(stderr, "Error: Stack underflow\n"); exit(EXIT_FAILURE); } return stack[top--]; } int evaluate(char *expr) { int i, op1, op2, result; char c; for (i = 0; expr[i] != '\0'; i++) { c = expr[i]; if (isdigit(c)) { push(c - '0'); } else if (is_operator(c)) { op2 = pop(); op1 = pop(); switch (c) { case '+': push(op1 + op2); break; case '-': push(op1 - op2); break; case '*': push(op1 * op2); break; case '/': push(op1 / op2); break; } } } result = pop(); if (top >= 0) { fprintf(stderr, "Error: Invalid expression\n"); exit(EXIT_FAILURE); } return result; } int main() { char expr[] = "3 4 + 5 * 6 -"; // 示例表达式 int result = evaluate(expr); printf("Result = %d\n", result); return 0; } ``` 这个程序使用了栈来实现逆波兰表达式求值,先遍历逆波兰表达式中的每个字符,若是数字则入栈,若是运算符则弹出栈顶的两个元素进行运算,并将结果入栈。最终,栈中只剩下一个元素,即为表达式的值。注意,这个程序并没有进行错误处理,例如输入表达式中出现非法字符或栈溢出等情况。在实际使用中,需要根据具体情况进行错误处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值