栈的运用——四则运算表达式求值
文章目录
在日常中我们在计算时常常会用到计算机中的计算器进行计算,在这篇文章中我将会简单介绍介绍在计算机中进行四则运算的原理。
逆波兰表达式(后缀表达式)
简介
当我们直接看到9+(3-1)×3+10÷2
这样带有四则运算的式子,我们能够进行简单的心算得出答案是20。但是在计算器中,计算机不能向我们人一样知道乘除的优先级大于加减,加括号的内容需要先行运算。
于是,想要让计算机能够明白乘除在加减后面但是要先进行运算,在括号中的内容也要提前优先级。
关于括号的问题似乎还是比较简单解决的,由于括号为成对存在的,只要读到左括号就进行进栈,在读到右括号的时候,让左括号出栈,期间的数字进行运算,具体的括号匹配算法可以详见下面这一篇博客。
但是即使解决了所谓的括号匹配,在进行关于先乘除后加减的处理时仍然复杂。
逆波兰表达式的定义
于是聪明的前辈波兰逻辑学家(J・ Lukasiewicz
),发明了一种无需括号的后缀表达法,我们称之为逆波兰表达式。
相对于9+(3-1)×3+10÷2
这个式子的逆波兰表达式为9 3 1 - 3 * + 10 2 / +
,这样的式子我们也称之为后缀表达式,不难发现得到的后缀表达式并没有括号的出现,被叫做后缀的原因其实是因为四则运算式的加减乘除等符号都在式子的后面出现。关于如何通过计算式得到后缀表达式,我们先按下不表,先看看这个后缀表达式在计算机里的表现吧。
在用后缀表达式计算的时候,我们使用栈的结构进行运算,其运算符合一下规则:
- 从左到右遍历给出的计算式,遇到数字就进栈,遇到就算符号就将栈顶的两个数字进行弹出,根据该符号进行运算,一直得到最终的运算结果。
逆波兰表达式运算步骤:
-
先创建一个空栈并进行初始化,供我们进行数据的存放。
typedef struct { int top; int items[MAX_STACK_SIZE]; } Stack; void initializeStack(Stack *stack) { stack->top = -1; }
-
从左开始读取表达式,先将前三个数字
9 3 1
进栈 -
接着读取到了
-
,将1
先从栈顶弹出作为减数,再将3
从栈顶弹出作为被减数,经过运算得到2,再将其进栈。 -
接着将数字
3
进栈 -
再读取
*
,3
和2
进行相乘,得到结果6
在进行入栈 -
后一个符号为
+
,计算6+9
,得到15
-
10 2
两个数字接着进栈 -
而后的符号为
-
,因此栈顶的第一个元素作为被除数,第二个元素则为除数,计算10/2
可得5 -
最后一个符号为
+
,所以15+5=20,20即为我们得出的最后的结果
c语言实现逆波兰表达式
以下为用c语言实现的逆波兰表达式的计算:
假设我们的逆波兰表达式中间用空格分隔开来
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define MAX_STACK_SIZE 100
typedef struct {
int top;
int items[MAX_STACK_SIZE];
} Stack;
void initializeStack(Stack *stack) {
stack->top = -1;
}
void push(Stack *stack, int value) {
if (stack->top == MAX_STACK_SIZE - 1) {
printf("栈满了\n");
exit(EXIT_FAILURE);
}
stack->items[++stack->top] = value;
}//入栈
int pop(Stack *stack) {
if (stack->top == -1) {
printf("栈为空\n");
exit(EXIT_FAILURE);
}
return stack->items[stack->top--];
}//出栈
int evaluateRPN(char *tokens) {
Stack stack;
initializeStack(&stack);
char *token = strtok(tokens, " ");//从字符串中提取第一个标记(token)并将其赋值给token。
while (token != NULL) {
if (isdigit(token[0]) || (token[0] == '-' && isdigit(token[1]))) {
push(&stack, atoi(token));//将数字元素从字符类型转化为int类型,并压入栈中
} else {
int operand2 = pop(&stack);
int operand1 = pop(&stack);//将栈顶的元素分别取出
switch (token[0]) {
case '+':
push(&stack, operand1 + operand2);
break;
case '-':
push(&stack, operand1 - operand2);
break;
case '*':
push(&stack, operand1 * operand2);
break;
case '/':
if (operand2 == 0) {
printf("除数不能为0\n");
exit(EXIT_FAILURE);
}
push(&stack, operand1 / operand2);
break;
default:
printf("非法的符号\n");
exit(EXIT_FAILURE);
}
}
token = strtok(NULL, " ");//更新token
}
return pop(&stack);
}
int main() {
char expression[] = "9 3 1 - 3 * + 10 2 / +"; // 示例的逆波兰表达式
int result = evaluateRPN(expression);
printf("Result: %d\n", result);
return 0;
}
通过这个函数我们能用c语言模拟计算器的计算过程。
大家可以在力扣上进行练习:
[150. 逆波兰表达式求值 - 力扣(LeetCode)]()
中缀表达式转后缀表达式
我们现在知道了我们如果将计算式转化为后缀表达式,我们就可以通过程序来计算出计算式(即中缀表达式)的结果,那么问题来了,我们应该如何将我们看到的计算式转化为后缀表达式呢?
这个转化的过程其实还是需要我们用到栈的结构特点。
将中缀表达式转后缀表达式的步骤
将中缀表达式转化为后缀表达式,满足以下规则:
- 从左往右遍历中缀表达式,如果读到数字那么就直接进行输出;如果读到的是符号的话,则与栈顶元素进行一次判断,判断其优先级,如果是读到的符号优先级小于栈顶符号(即先乘除后加减),则栈顶元素依次进行输出,而后将当前符号进栈(即循环输出栈顶元素,直到栈顶优先级小于读取到的符号,才将其入栈);如果读到右括号,则循环输出栈顶元素,直到将左括号出栈。重复以上循环到后缀表达式结束为止。
我们仍然以9+(3-1)*3+10/2
为例,模拟一遍转化的流程
-
当我们遇到数字的时候就直接输出,所以第一个数字
9
就将9
进行输出,并将后面的+
存入栈中。 -
第三个字符仍为
(
,为左括号,还未与右括号进行配对,仍然进栈 -
第四个字符为
3
,立马进行输出,此时输出的总式为9 3
,接着为-
进栈 -
接下来读取
3
,此时3
的后面为)
,为了匹配前面的(
,我们要使栈顶元素依次出栈,直到(
出栈为止,而此时(
之上的符号只有-
,因此-
弹出,总表达式为9 3 1 -
。 -
读取数字
3
,紧接着是符号*
,此时栈顶符号位+
,优先级+
小于*
,因此栈顶元素不弹出,*
进栈 -
之后的符号为
+
,比栈顶符号*
优先级低,所以栈顶元素*
弹出,由于没有符号比+
的优先级更低,所以*
和+
依次弹出,直到栈为空才将+
压入栈中 -
然后读到
10
,直接输出,而后符号是/
,优先级比+
高,所以压入栈中。 -
最后读取数字
2
,输出之后,由于已经将中缀表达式遍历完成,所以我们应该将先前栈中存储的符号,全部输出,因此我们成功的将中缀表达式9+(3-1)*3+10/2
转化为了后缀表达式9 3 1 - 3 * + 10 2 / +
用c语言实现中缀表达式转后缀表达式
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define MAX_SIZE 100
// 结构体定义栈
struct Stack {
int top;
unsigned capacity;
char *array;
};
// 创建一个栈
struct Stack* createStack(unsigned capacity) {
struct Stack* stack = (struct Stack*)malloc(sizeof(struct Stack));
stack->capacity = capacity;
stack->top = -1;
stack->array = (char*)malloc(stack->capacity * sizeof(char));
return stack;
}
// 检查栈是否已满
int isFull(struct Stack* stack) {
return stack->top == stack->capacity - 1;
}
// 检查栈是否为空
int isEmpty(struct Stack* stack) {
return stack->top == -1;
}
// 入栈操作
void push(struct Stack* stack, char item) {
if (isFull(stack))
return;
stack->array[++stack->top] = item;
}
// 出栈操作
char pop(struct Stack* stack) {
if (isEmpty(stack))
return '\0';
return stack->array[stack->top--];
}
// 获取栈顶元素
char peek(struct Stack* stack) {
if (isEmpty(stack))
return '\0';
return stack->array[stack->top];
}
// 检查运算符的优先级
int precedence(char op) {
switch (op) {
case '+':
case '-':
return 1;
case '*':
case '/':
return 2;
case '^':
return 3;
}
return -1;
}
// 中缀表达式转后缀表达式
void infixToPostfix(char* infix, char* postfix) {
struct Stack* stack = createStack(strlen(infix));
int i, k;
for (i = 0, k = -1; infix[i]; ++i) {
if (isdigit(infix[i])) {
while (isdigit(infix[i])) {
postfix[++k] = infix[i];
i++;
}
postfix[++k] = ' '; // 添加空格以分隔数字
--i;
}
else if (infix[i] == '(')
push(stack, infix[i]);
else if (infix[i] == ')') {
while (!isEmpty(stack) && peek(stack) != '('){
postfix[++k] = pop(stack);
postfix[++k] = ' ';
}
if (isEmpty(stack))
return; //不能匹配到相应的左括号,输入的中缀表达式错误
else
pop(stack);
} else {
while (!isEmpty(stack) && precedence(infix[i]) <= precedence(peek(stack))){
postfix[++k] = pop(stack);
postfix[++k] = ' ';
}
push(stack, infix[i]);
}
}
while (!isEmpty(stack)){
postfix[++k] = pop(stack);
postfix[++k] = ' ';
}
postfix[++k] = '\0';
}
int main() {
char infix[MAX_SIZE], postfix[MAX_SIZE];
printf("请输入中缀表达式: ");
fgets(infix, MAX_SIZE, stdin);
infixToPostfix(infix, postfix);
// 输出后缀表达式并在每个元素之间添加空格
printf("该中缀表达式的后缀表达式为:\n", postfix);
return 0;
}
参考资料:
《大话数据结构》 程勇 著