【数据结构】利用栈实现正整数四则运算
Published:
栈的基本知识
首先,什么是栈。
栈是一种线性的数据结构,只允许在一端进行数据的插入和删除。简单来说,栈是用来存储数据的,有头(栈底)有尾(栈顶),但是只允许从栈顶取出数据(出栈),或者把数据放到栈顶(入栈)。下面这幅图中,用蓝色长方形表示栈中存储的数据,最底部是栈底,最上部是栈顶。你只能把数据(蓝色长方形)从上面拿走(pop),或者从最上面添加新的数据(push)。即后入栈的元素可以先出栈(Last in First out – LIFO)。
在 C++ 中,有封装好的栈类:stack,支持数据的存取。
// 头文件
#include <stack>
// 栈的初始化
stack<int> stack;
// 入栈
stack.push(21);
// 出栈
stack.pop();
// 检查栈是否为空
stack.empty()
// 获取栈顶元素
int elem = stack.top();
在 Python 中,list 支持栈的各种操作。
# 创造一个空栈
stack = []
# 入栈
stack.append(3)
# 出栈
elem = stack.pop()
# 检查栈是否为空(栈中元素个数是否为0)
len(stack) > 0
# 获取栈顶元素
elem = stack[-1]
逆波兰表示
基础知识介绍完了,进入正题:如何编写一个程序来进行四则运算呢?
我们看到 12 +(3 + 4)\(\times\) 5 / 7 可以很快就算出来答案是 17,因为我们知道要先计算括号里面的加法,然后先乘除,后加减,最后得到计算结果。可是对计算机程序来说,输入是一个字符串,需要区分数字、运算符、括号,然后需要按照前面的运算顺序执行,才能得到结果。
为了更好地编程实现四则元算,我们要引入一个新的算数表示法——逆波兰表示,之所以叫这个名,是因为它的发明者是一位波兰的数学家。在逆波兰表示中,操作符( +,-,\(\times\),/)是位于操作数后面的,而不是位于中间。举个简单的例子,3 + 4 的逆波兰表示就是 3 4 +。12 +(3 + 4)\(\times\) 5 / 7 的逆波兰表示是 12 3 4 + 5 7 / \(\times\) +。
逆波兰表示转化的算法及代码
如何把我们熟悉的四则运算表达式转化为逆波兰表示呢?
还是要利用栈的后进先出特性。下面简要描述将常规四则元算表达式转化为逆波兰表示的步骤:
创建一个空栈。
从左至右遍历常规四则元算表达式的字符,对每一个字符:
1. 若字符是数字,则将其添加到输出的表达式的尾端;
2. 若字符是左括号 "(",则将其入栈;
3. 若字符是 "+" 或者 "-",则把栈中元素依次出栈,添加到输出表达式的末尾,直到栈顶元素是左括号 "(" ,
或者栈变为空栈,再把字符入栈;
4. 若字符是 "$$\times$$" 或者 "/",则将其入栈;
5. 若字符是有括号 ")",则把栈中元素依次出栈,添加到输出表达式末尾,直到栈顶元素是是左括号 "(",然后将左括号出栈。
遍历玩所有字符以后,如果栈非空,则将栈中元素依次出栈,添加到输出表达式末尾。
下图显示了将 12 +(3 + 4)\(\times\) 5 / 7 转化为逆波兰表示过程中,栈中元素以及输出表达式的变化。
下面是将输入常规四则运算表达式变为逆波兰表示的 Python 代码。
in_expression = "12+(3+4)*5/7"
stack = []
out_expression = ""
for c in in_expression:
if c == " ":
continue
if c == "(":
stack.append(c)
elif c == "+" or c == "-":
out_expression += " "
if len(stack) == 0:
stack.append(c)
else:
while len(stack) > 0 and stack[-1]!= "(":
out_expression += stack.pop()
stack.append(c)
elif c == "*" or c == "/":
out_expression += " "
stack.append(c)
elif c == ")":
out_expression += " "
while len(stack) > 0 and stack[-1] != "(":
out_expression += stack.pop()
if len(stack) > 0:
stack.pop()
else:
out_expression += c
while len(stack) > 0:
out_expression += stack.pop()
print("in expression :" + in_expression)
print("out expression :" + out_expression)
逆波兰表达式求值
有了逆波兰表达式,求值就变的很方便了。还是需要利用栈后进先出的特性。下面是对逆波兰表示的求值步骤:
创建一个空栈。
从左至右遍历逆波兰表达式:
1. 若当前元素为数值,则入栈;
2. 若当前元素为运算符,则将栈顶两个元素出栈,以它们作为操作数,当前元素为操作符,进行运算,将运算结果入栈。
当遍历完成时,栈顶元素出栈,即为最终运算结果。
注意,在逆波兰表示中,连续的数字需要用一定的标记符号来隔开,在这里,我用的是空格符号。在上面的算法中,没有提到这一点,但是在写代码的时候,需要注意。
下面是遍历逆波兰表达式 12 3 4 + 5 7 / \(\times\) + 求值的过程。
下面是利用逆波兰表达式求值的 Python 代码。
out_expression = "12 3 4 + 5 7/*+"
stack = []
i = 0
while i < len(out_expression):
if out_expression[i] in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']:
j = i + 1
while j < len(out_expression) and out_expression[j] in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']:
j += 1
stack.append(int(out_expression[i:j]))
i = j
continue
elif out_expression[i] == '+':
num2 = stack.pop()
num1 = stack.pop()
stack.append(num1 + num2)
elif out_expression[i] == '-':
num2 = stack.pop()
num1 = stack.pop()
stack.append(num1 - num2)
elif out_expression[i] == '*':
num2 = stack.pop()
num1 = stack.pop()
stack.append(num1 * num2)
elif out_expression[i] == '/':
num2 = stack.pop()
num1 = stack.pop()
stack.append(num1 / num2)
i += 1
print(stack[-1])