一、栈的应用-十进制转换
十进制转换为二进制
- 所谓的“进制”,就是用多少个字符来表示整数,十进制是0~9这十个数字字符,二进制是0、1两个字符
- 十进制转换为二进制,采用的是“除以2求余数”的算法
- “除以2”的过程,得到的余数是从低到高的次序,而输出则是从高到低,所以需要一个栈来反转次序
示例:十进制转换为二进制
def dec_to_bin(dec_number):
s = Stack()
while dec_number > 0:
rem = dec_number % 2 # 取余
s.push(rem)
dec_number = dec_number // 2 # 取整
result = ""
while not s.is_empty():
result += str(s.pop())
return result
print(dec_to_bin(10))
print(dec_to_bin(63))
### 输出结果
1010
111111
扩展到更多进制转换
- 二进制有两个不同数字0、1
- 十进制有十个不同数字0、1、2、3、4、5、6、7、8、9
- 八进制可用八个不同数字0、1、2、3、4、5、6、7
- 十六进制的十六个不同数字则是0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F
示例:十进制转换为任意进制
def dec_to_n(dec_number, base):
digits = "0123456789ABCDEF"
s = Stack()
while dec_number > 0:
rem = dec_number % base # 取余
s.push(rem)
dec_number = dec_number // base # 取整
result = ""
while not s.is_empty():
result += digits[s.pop()]
return result
print(dec_to_n(10, 2))
print(dec_to_n(63, 2))
print(dec_to_n(63, 8))
print(dec_to_n(63, 16))
### 输出结果
1010
111111
77
3F
二、栈的应用-表达式转换
三种表达式
中缀表达式
A+B*C
这种操作符(operator)介于操作数(operand)中间的表示法,称为“中缀”表示法- 但有时候中缀表示法会引起混淆,人们引入了操作符“优先级”的概念来消除混淆,同时引入了括号来表示强制优先级,括号的优先级最高,而且在嵌套的括号中,内层的优先级更高
前缀和后缀表达式
- “前缀”和“后缀”表示法:以操作符相对于操作数的位置来定义
- 中缀表达式A+B,将操作符移到前面,变为“+AB”,或者将操作符移到最后,变为“AB+”
- 在前缀和后缀表达式中,操作符的次序完全决定了运算的次序,不再有混淆
中缀表达式infix | 前缀表达式prefix | 后缀表达式postfix |
---|---|---|
A + B * C + D | ++A * B C D | A B C * + D + |
(A + B) * (C + D) | * + A B + C D | A B + C D + * |
A * B + C * D | * + * A B C D | A B * C D * + |
A + B + C + D | +++ A B C D | A B + C + D + |
中缀表达式转后缀表达式
分析
- 中缀表达式
A+B*C
,其对应的后缀表达式是ABC*+
。操作数ABC的顺序没有改变,操作符的出现顺序,在后缀表达式中反转了。由于*的优先级比+高,所以后缀表达式中操作符的出现顺序与运算次序一致。 - 在中缀表达式转换为后缀形式的处理过程中,操作符比操作数要晚输出。所以在扫描到对应的第二个操作数之前,需要把操作符先保存起来。而这些暂存的操作符,由于优先级的规则,还有可能要反转次序输出。这种反转特性,使得我们考虑用栈来保存暂时未处理的操作符。
(A+B)*C
,对应的后缀形式是AB+C*
,这里+
的输出比*
要早,主要是因为括号使得+
的优先级提升,高于括号之外的*
。所以遇到左括号,要标记下,其后出现的操作符优先级提升了,一旦扫描到对应的右括号,就可
以马上输出这个操作符。
算法描述
-
从左到右扫描中缀表达式单词列表
- 如果单词是操作数,则直接添加到后缀表达式列表的末尾
- 如果单词是左括号“(”,则压入栈顶
- 如果单词是右括号“)”,则反复弹出栈顶操作符,加入到输出列表末尾,直到碰到左括号
- 如果单词是操作符“*/±”,则压入栈顶。但在压入之前,要比较其与栈顶操作符的优先级,如果栈顶的高于或等于它,就要反复弹出栈顶操作符,加入到输出列表末尾。直到栈顶的操作符优先级低于它。
-
中缀表达式单词列表扫描结束后,把栈中的所有剩余操作符依次弹出,添加到输出列表末尾
import string
from my_stack import Stack
def infix_to_postfix(infix_expr: str):
# 运算符优先级
priority_dict = {"(": 1, "+": 2, "-": 2, "*": 3, "/": 3}
s = Stack()
# 删除表达式中的所有空格,并转换为列表
infix_list = list(infix_expr.replace(" ", ""))
postfix_list = []
for i in infix_list:
if i in (string.ascii_letters + string.digits):
postfix_list.append(i)
elif i == "(":
s.push(i)
elif i == ")":
while s.peek() != "(":
postfix_list.append(s.pop())
else:
while not s.is_empty() and priority_dict[s.peek()] >= priority_dict[i]:
postfix_list.append(s.pop())
s.push(i)
# 栈中剩余操作符依次弹出
while not s.is_empty():
postfix_list.append(s.pop())
# 将列表拼接为字符串,并删除(符号
return "".join(postfix_list).replace("(", "")
print(infix_to_postfix("A+B*C"))
print(infix_to_postfix("(A+B)*C"))
print(infix_to_postfix("A*B+C*D"))
print(infix_to_postfix("(A + B) * (C + D)"))
print(infix_to_postfix("A + B + C + D"))
### 输出结果
ABC*+
AB+C*
AB*CD*+
AB+CD+*
AB+C+D+
三、栈的应用-后缀表达式求值
分析
-
在对后缀表达式从左到右扫描的过程中,由于操作符在操作数的后面,所以要暂存操作数,在碰到操作符的时候,再将暂存的两个操作数进行实际的计算。这仍然是栈的特性。
-
例如
4 5 6 * +
,先扫描到4、5两个操作数,但还不知道对这两个操作数能做什么计算。继续扫描,又碰到操作数6,继续暂存入栈。直到“*”,现在知道是栈顶两个操作数5、6做乘法。 -
先弹出的是右操作数,后弹出的是左操作数,这个对于
-和/
很重要。为了继续后续的计算,需要把这个中间结果30压入栈顶,继续扫描后面的符号。当所有操作符都处理完毕,栈中只留下1个操作数,就是表达式的值。
from my_stack import Stack
def do_math(op, num1, num2):
# 进行+-*/运算
return eval(num2 + op + num1)
def evaluate_postfix(expression: str):
# 约定:后缀表达式expression是用空格隔开的一系列单词(token)构成
expression_list = expression.split()
s = Stack()
for i in expression_list:
if i in "+-*/":
s.push(str(do_math(i, s.pop(), s.pop())))
else:
s.push(i)
return s.pop()
print(evaluate_postfix("4 5 6 * +"))
print(evaluate_postfix("7 8 + 3 2 + /"))
print(evaluate_postfix("17 18 + 13 12 + *"))
### 运行结果
34
3.0
875
您正在阅读的是《数据结构与算法Python版》专栏!关注不迷路~