python实现栈及栈在四则运算中的应用

定义栈类:

class Stack(object) :

    # 初始化栈为空列表
    def __init__(self):
        self.items = []

    # 判断栈是否为空,返回 True 或 False
    def is_empty(self):
        return self.items == []

    # 压栈,添加新元素进栈
    def push(self,item):
        self.items.append(item)

    # 出栈,删除栈顶元素,并返回
    # 注:列表的pop()方法用于根据索引删除并返回被删除的元素,没有传入索引参数,默认删除最后一个元素
    def pop(self):
        return self.items.pop()

    # 返回栈顶元素
    def peek(self):
        return self.items[len(self.items)-1]

    # 返回栈的大小
    def size(self):
        return len(self.items)
	
if __name__ == "__main__" :
    '''
    测试
    '''
    stack = Stack() # 栈类的一个实例对象
    stack.push("hello")
    stack.push("world")
    stack.push("chang")
    print(stack.is_empty()) # False
    print(stack.size())     # 3
    print(stack.peek())     # chang
    print(stack.pop())      # chang
    print(stack.pop())      # world
    print(stack.pop())      # hello
    print(stack.is_empty()) # True

PS:从定义栈类的过程中可以看到,栈的一系列操作其实就是列表的操作,当不想通过定义栈类再来实现功能的话,可以直接使用列表来实现


栈的应用之四则运算表达式求值:

步骤:中缀表达式转后缀表达式,通过后缀表达式计算一个算数表达式的值

中缀表达式和后缀表达式的概念:

平时用到的标准的四则运算表达式就叫做中缀表达式,例如“9 +(3 - 1) * 3 + 10 / 2”,特点是运算符在数字中间;

后缀表达式就是一种把运算符放在数字后面的形式,“9 3 1 - 3 * + 10 2 / +” 即为上述中缀表达式对应的后缀表达式形式,后缀表达式还有一个特点就是消除了所有的括号;

中缀表达式能够非常直观地展示出运算关系,很方便手动计算,但是如果要设计一个计算机程序来计算这个表达式却变得非常繁琐,不仅要考虑四则运算的优先级,还要考虑括号的影响,而后缀表达式虽然在表现形式上不直观却非常便于计算机进行计算


中缀表达式转化为后缀表达式的规则:

中缀表达式转化为后缀表达式要借助栈来实现,这里的栈用于储存运算符号而不是数值。

规则: 从左到右遍历中缀表达式中的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号则要分为两种情况:

  1. 是括号时,如果是左括号,直接将左括号入栈,如果是右括号则栈顶元素依次出栈并输出,直到有一个左括号出栈(出栈的左括号不输出到后缀表达式)

  2. 是运算符号时,如果栈顶符号为左括号,则直接将这个运算符号入栈。栈顶符号不为左括号时,如果该运算符号优先级比栈顶运算符号高则入栈,比栈顶符号低或者相等时则栈顶元素依次出栈并输出直到栈为空或者栈顶为左括号为止,然后将这个符号入栈

    最后将栈顶符号依次出栈并输出,得到的结果即为最终的后缀表达式。

实例:

以中缀表达式“9 +(3 - 1) * 3 + 10 / 2”为例推导其后缀表达式的步骤如下:
1、初始化一个空栈

2、第一个数字9直接输出到后缀表达式,第二个符号“+”入栈,此时后缀表达式:9 栈内符号:+

3、接下里左括号入栈,数字3输出,此时后缀表达式为: 9 3 栈内元素为:+ (

4、接下来是减号“-”,由于栈顶为左括号,直接入栈,数字1输出,此时后缀表达式:9 3 1 栈内符号:+ ( -

5、接下来是右括号,栈顶符号“-”出栈并输出,接着栈顶左括号出栈,此时后缀表达式:9 3 1 - 栈内符号:+

6、接下来是乘号“*”,比栈顶符号加号“+”优先级高,入栈,数字3输出,此时后缀表达式:9 3 1 - 3 栈内符号:+ *

7、接下来是加号“+”,比栈顶 * 优先级低,栈顶 * 出栈并输出,接着与新的栈顶加号“+”比较优先级相同,栈顶“+”出栈并输出,栈为空,到此为止,然后将加号“+”入栈。此时后缀表达式为:9 3 1 - 3 * + 栈内符号:+

8、接下来数字10输出,除号“/”优先级比栈顶加号“+”优先级高直接入栈,此时后缀表达式:9 3 1 - 3 * + 10 栈内为:+ /

9、最后数字2输出,遍历结束后,栈顶符号依次出栈并输出,得到最终的后缀表达式:9 3 1 - 3 * + 10 2 / +

假设我们已经创建好了一个栈类,直接使用……
以下代码为将中缀表达式转化为后缀表达式

def compare(op1,op2) :
    '''
    比较两个运算符的优先级,乘除运算的优先级比加减高
    op1优先级比op2高返回True,否则返回False
    '''
    return op1 in ["*","/"] and op2 in ["+","-"]

# 中缀表达式转化为后缀表达式
s = "9+(3-1)*3+10/2" # 中缀表达式
stack_opt = Stack()  # 栈 stack_opt 用于储存运算符号
i = 0
temp_expression = '' # temp_expression 是后缀表达式
while i < len(s) :   # 遍历中缀表达式

    if s[i].isdigit() :
        '''如果s[i]是数字,需要再判断一下它后边的是不是也是数字,例如数字 10 ,它是一个整体 10,不是1和0'''
        start = i
        while i + 1 < len(s) and s[i + 1].isdigit():
            i += 1
        temp_expression += s[start:i+1] + " " # 此处是加了一个空格, i为连续的数字字符中的最后一个数字字符的位置
        
    elif stack_opt.size() == 0 or stack_opt.peek() == "(" : # 栈为空,或者栈顶为左括号,操作符直接入栈stack_opt
        stack_opt.push(s[i])
        
    elif s[i] == "(" or compare(s[i],stack_opt.peek()):     # 当前操作符为左括号或者比栈顶操作符的优先级高,操作符直接入栈
        stack_opt.push(s[i])
        
    elif s[i] == ")" : # 遇见右括号,栈中的操作符逐一出栈并输出,直到遇到左括号,此时左括号也出栈,但是不写进表达式中
        while stack_opt.peek() != "(" :
            temp_expression += stack_opt.pop() + " "
        stack_opt.pop() # 左括号出栈
        
    else :
        while stack_opt.size() != 0 : # 当前符号比栈顶运算符的优先级低或者相等时,栈中的元素依次出栈并输出直到栈为空或者栈顶为左括号为止
            if stack_opt.peek() == "(" :
                break
            temp_expression += stack_opt.pop() + " "
        stack_opt.push(s[i]) # 注意出栈结束之后,当前的这个符号还要入栈
    i += 1

while stack_opt.size() : # 最后一步:将栈中剩余的运算符依次输出
    temp_expression += stack_opt.pop() + " "

print(temp_expression)
# 输出结果:9 3 1 - 3 * + 10 2 / + 

通过后缀表达式计算出结果:

后缀表达式的计算要借助栈来实现,注意:计算中缀表达式时使用的栈是用来储存运算符号,而后缀表达式的栈储存的是数值

规则: 从左到右遍历表达式的每个数字和符号,遇到的是数字就进栈,遇到的时符号就将栈顶的两个数字出栈进行计算,然后将计算结果入栈,最终栈里的值即为计算的结果。

以中缀表达式“9 +(3 - 1) * 3 + 10 / 2”的后缀表达式“9 3 1 - 3 * + 10 2 / +”为例,计算过程如下:

1、初始化一个空栈

2、后缀表达式前三个都是数字,所以9、3、1依次入栈,此时栈内元素为(最左边为栈底元素):9 3 1

3、接下来为减号“-”,此时1出栈作为第二个数,3出栈作为第一个数(因为栈内元素是先进后出,所以最先出栈的为运算中的第二个数,接着出栈的才是运算中的第一个数),进行进行减法运算3 - 1 = 2 然后入栈,此时栈内元素为:9 2

4、接下来为数字3,进栈,栈内元素为:9 2 3

5、接下来为乘号“*”,此时3、2出栈,进行乘法运算2 * 3 = 6,结果入栈,此时栈内元素为:9 6

6、接下来是加号“+”,9、6出栈进行加法运算,结果15入栈,此时栈内元素为:15

7、接下来是数字10、2入栈,此时栈内元素为:15 10 2

8、接下来是除号“/”,10、2出栈进行除法运算,结果5入栈,此时栈内元素为:15 5

9、最后一个是加号“+”,15、5出栈进行加法运算,结果20即为整个表达式的运算结果,与中缀表达式计算结果一致

只需要按顺序遍历就能够计算出结果,不用考虑对四则运算法则做复杂的逻辑处理,对计算机处理来说是非常方便的。

我们已经创建好了一个栈类,并且得到了“9 +3 - 1) * 3 + 10 / 2”的后缀表达式“9 3 1 - 3 * + 10 2 / + ”,它带有空格
以下代码为计算后缀表达式

temp_expression = "9 3 1 - 3 * + 10 2 / + "
def calculate(num1,num2,operator) :
    ''' 根据运算符号operator计算结果并返回 '''
    if operator == "+" :
        return int(num1) + int(num2)
    elif operator == "-" :
        return int(num1)  - int(num2)
    elif operator == "*" :
        return int(num1)  * int(num2)
    else :
        return int(num1)  / int(num2)

def process(data,operator) :
    ''' data出栈两个数值,进行一次计算,并将运算结果入栈data '''
    num2 = data.pop()
    num1 = data.pop()
    data.push(calculate(num1,num2,operator))

nums = Stack() # 这个栈用来储存数值
j = 0
while j < len(temp_expression) :
    if temp_expression[j].isdigit() :
        '''如果是数字,需要再判断一下它后边的是不是也是数字,例如数字 10 ,它是一个整体 10,不是1和0'''
        start = j
        while j + 1 < len(temp_expression) and temp_expression[j + 1].isdigit():
            j += 1
        nums.push(temp_expression[start:j+1]) # 将数值存入栈中

    elif not temp_expression[j].isspace() : # 因为我们的后缀表达式中带有空格,所以在不是数字,也不是空格的情况下,就是运算符号了,此时开始运算
        process(nums,temp_expression[j])    # 将运算符作为第二个参数传入
    j += 1
    
print(nums.pop()) # 运算完成后,栈中只有一个元素,就是最终结果 20

优化与整合:

通过前面的介绍,只需要经过将中缀表达式转化为后缀表达式,再计算后缀表达式这两步就能得到一个四则运算表达式的值。这两步中各用到了一个栈,推导后缀表达式时用到的栈存储的是运算符号以及括号(只有左括号,没有右括号),计算后缀表达式时用到的栈存储的是数字,两个步骤分开先后执行要遍历一次中缀表达式载遍历一遍后缀表达式。同时在中缀表达式推导后缀表达式以及计算后缀表达式的过程中,都是要对字符串进行操作,如果将两步合并同时执行的话,不仅能够简化代码,更能提高运算效率,因为这样全程只需要遍历一次中缀表达式就可以完成计算。

思路:用一个栈data保存运算数字,一个栈opt保存运算符号。从左到右遍历中缀表达式,如果是数字就入栈data。
如果是符号,以下四种情况直接将符号入栈opt:

  • 栈为空;
  • 栈顶为左括号;
  • 该符号为左括号;
  • 该运算符号优先级比栈顶符号高。

如果是右括号,则执行一次计算步骤:从opt出栈一个运算符号,从data出栈两个数字进行一次运算并将结果入栈data。重复执行该计算步骤,直到opt栈顶为左括号,然后将该左括号出栈;如果该符号优先级低于opt栈顶符号或者与栈顶符号优先级相同时,重复执行与之前相同的计算步骤,直到opt栈为空,若中途opt栈顶符号为左括号则停止执行计算步骤。中缀表达式遍历完成后,继续执行之前的计算步骤直到opt栈为空。

来看大神(不是我~~~)的优化代码:

def compare(op1, op2):
    """
    比较两个运算符的优先级,乘除运算优先级比加减高
    op1优先级比op2高返回True,否则返回False
    """
    return op1 in ["*", "/"] and op2 in ["+", "-"]
 
def getvalue(num1, num2, operator):
    """
    根据运算符号operator计算结果并返回
    """
    if operator == "+":
        return num1 + num2
    elif operator == "-":
        return num1 - num2
    elif operator == "*":
        return num1 * num2
    else:  # /
        return num1 / num2
 
def process(data, opt):
    """
    opt出栈一个运算符,data出栈两个数值,进行一次计算,并将结果入栈data
    """
    operator = opt.pop()
    num2 = data.pop()
    num1 = data.pop()
    data.append(getvalue(num1, num2, operator))
 
def calculate(s):
    """
    计算字符串表达式的值,字符串中不包含空格
    """
    data = []  # 数据栈
    opt = []  # 操作符栈
    i = 0  # 表达式遍历索引
    while i < len(s):
        if s[i].isdigit():  # 数字,入栈data
            start = i  # 数字字符开始位置
            while i + 1 < len(s) and s[i + 1].isdigit():
                i += 1
            data.append(int(s[start: i + 1]))  # i为最后一个数字字符的位置
        elif s[i] == ")":  # 右括号,opt出栈同时data出栈并计算,计算结果入栈data,直到opt出栈一个左括号
            while opt[-1] != "(":
                process(data, opt)
            opt.pop()  # 出栈"("
        elif not opt or opt[-1] == "(":  # 操作符栈为空,或者操作符栈顶为左括号,操作符直接入栈opt
            opt.append(s[i])
        elif s[i] == "(" or compare(s[i], opt[-1]):  # 当前操作符为左括号或者比栈顶操作符优先级高,操作符直接入栈opt
            opt.append(s[i])
        else:  # 优先级不比栈顶操作符高时,opt出栈同时data出栈并计算,计算结果如栈data
            while opt and not compare(s[i], opt[-1]):
                if opt[-1] == "(":  # 若遇到左括号,停止计算
                    break
                process(data, opt)
            opt.append(s[i])
        i += 1  # 遍历索引后移
    while opt:
        process(data, opt)
    print(data.pop())
 
if __name__ == '__main__':
    exp = "(9+((3-1)*3+10/2))*2"
    calculate(exp)

猜你喜欢

转载自blog.csdn.net/weixin_43974265/article/details/104967276
今日推荐