微信登录

栈 - 栈的应用 - 表达式求值、括号匹配等

栈 - 栈的应用 - 表达式求值、括号匹配等

一、引言

在计算机科学的世界里,栈(Stack)是一种非常重要且基础的数据结构。它遵循后进先出(Last In First Out,LIFO)的原则,就像一摞盘子,最后放上去的盘子总是最先被拿走。栈的这一特性使得它在许多算法和实际应用中发挥着关键作用,本文将重点探讨栈在表达式求值和括号匹配这两个常见场景中的应用。

二、栈的基本概念回顾

栈是一种线性数据结构,它只允许在一端进行插入(入栈,Push)和删除(出栈,Pop)操作。这一端被称为栈顶,另一端则称为栈底。除了入栈和出栈操作外,栈通常还支持查看栈顶元素(Peek)和判断栈是否为空(IsEmpty)等操作。

下面是一个简单的 Python 代码示例,实现了一个基本的栈类:

  1. class Stack:
  2. def __init__(self):
  3. self.items = []
  4. def is_empty(self):
  5. return len(self.items) == 0
  6. def push(self, item):
  7. self.items.append(item)
  8. def pop(self):
  9. if self.is_empty():
  10. return None
  11. return self.items.pop()
  12. def peek(self):
  13. if self.is_empty():
  14. return None
  15. return self.items[-1]
  16. def size(self):
  17. return len(self.items)

三、栈在表达式求值中的应用

(一)中缀表达式、前缀表达式和后缀表达式

在数学中,我们常见的表达式是中缀表达式,即运算符位于操作数之间,例如 3 + 4 * 2。但在计算机处理表达式时,前缀表达式(运算符在操作数之前,如 + 3 * 4 2)和后缀表达式(运算符在操作数之后,如 3 4 2 * +)更为方便。

(二)中缀表达式转后缀表达式

中缀表达式转后缀表达式可以借助栈来实现,具体步骤如下:

  1. 初始化一个空栈用于存储运算符,一个空列表用于存储后缀表达式。
  2. 从左到右扫描中缀表达式的每个元素:
    • 如果是操作数,直接添加到后缀表达式列表中。
    • 如果是左括号,将其压入栈中。
    • 如果是右括号,从栈中弹出运算符并添加到后缀表达式列表中,直到遇到左括号,然后将左括号弹出(但不添加到后缀表达式中)。
    • 如果是运算符,比较其与栈顶运算符的优先级:
      • 如果栈为空或栈顶是左括号,直接将该运算符压入栈中。
      • 否则,若该运算符优先级高于栈顶运算符,将其压入栈中;否则,弹出栈顶运算符并添加到后缀表达式列表中,直到满足压栈条件。
  3. 扫描结束后,将栈中剩余的运算符依次弹出并添加到后缀表达式列表中。

以下是 Python 代码实现:

  1. def infix_to_postfix(infix_expr):
  2. precedence = {'+': 1, '-': 1, '*': 2, '/': 2}
  3. stack = Stack()
  4. postfix_list = []
  5. token_list = infix_expr.split()
  6. for token in token_list:
  7. if token.isdigit():
  8. postfix_list.append(token)
  9. elif token == '(':
  10. stack.push(token)
  11. elif token == ')':
  12. top_token = stack.pop()
  13. while top_token!= '(':
  14. postfix_list.append(top_token)
  15. top_token = stack.pop()
  16. else:
  17. while (not stack.is_empty()) and (stack.peek() in precedence) and (precedence[stack.peek()] >= precedence[token]):
  18. postfix_list.append(stack.pop())
  19. stack.push(token)
  20. while not stack.is_empty():
  21. postfix_list.append(stack.pop())
  22. return " ".join(postfix_list)
  23. # 测试
  24. infix_expr = "3 + 4 * 2"
  25. postfix_expr = infix_to_postfix(infix_expr)
  26. print(f"中缀表达式: {infix_expr}")
  27. print(f"后缀表达式: {postfix_expr}")

(三)后缀表达式求值

后缀表达式求值同样可以使用栈来完成,具体步骤如下:

  1. 初始化一个空栈。
  2. 从左到右扫描后缀表达式的每个元素:
    • 如果是操作数,将其压入栈中。
    • 如果是运算符,从栈中弹出两个操作数,进行相应的运算,并将结果压入栈中。
  3. 扫描结束后,栈中剩下的唯一元素就是表达式的值。

以下是 Python 代码实现:

  1. def evaluate_postfix(postfix_expr):
  2. stack = Stack()
  3. token_list = postfix_expr.split()
  4. for token in token_list:
  5. if token.isdigit():
  6. stack.push(int(token))
  7. else:
  8. operand2 = stack.pop()
  9. operand1 = stack.pop()
  10. result = do_math(token, operand1, operand2)
  11. stack.push(result)
  12. return stack.pop()
  13. def do_math(op, op1, op2):
  14. if op == '+':
  15. return op1 + op2
  16. elif op == '-':
  17. return op1 - op2
  18. elif op == '*':
  19. return op1 * op2
  20. elif op == '/':
  21. return op1 / op2
  22. # 测试
  23. postfix_expr = "3 4 2 * +"
  24. result = evaluate_postfix(postfix_expr)
  25. print(f"后缀表达式 {postfix_expr} 的值为: {result}")

四、栈在括号匹配中的应用

括号匹配是栈的另一个经典应用场景,在许多编程语言和文本处理中都有重要作用。例如,在编写代码时,编译器需要检查括号是否成对出现,以确保代码的语法正确性。

(一)算法思路

可以使用栈来实现括号匹配,具体步骤如下:

  1. 初始化一个空栈。
  2. 从左到右扫描字符串:
    • 如果遇到左括号,将其压入栈中。
    • 如果遇到右括号,检查栈是否为空:
      • 如果栈为空,说明右括号没有对应的左括号,返回 False
      • 否则,弹出栈顶元素,检查弹出的左括号与当前右括号是否匹配:
        • 如果不匹配,返回 False
  3. 扫描结束后,检查栈是否为空:
    • 如果栈为空,说明所有括号都匹配,返回 True
    • 否则,说明有左括号没有对应的右括号,返回 False

(二)Python 代码实现

  1. def is_matching_pair(left, right):
  2. if left == '(' and right == ')':
  3. return True
  4. elif left == '[' and right == ']':
  5. return True
  6. elif left == '{' and right == '}':
  7. return True
  8. return False
  9. def is_balanced(expression):
  10. stack = Stack()
  11. for char in expression:
  12. if char in '([{':
  13. stack.push(char)
  14. elif char in ')]}':
  15. if stack.is_empty():
  16. return False
  17. top = stack.pop()
  18. if not is_matching_pair(top, char):
  19. return False
  20. return stack.is_empty()
  21. # 测试
  22. expression1 = "{[()]}"
  23. expression2 = "{[(])}"
  24. print(f"表达式 {expression1} 是否括号匹配: {is_balanced(expression1)}")
  25. print(f"表达式 {expression2} 是否括号匹配: {is_balanced(expression2)}")

五、总结

应用场景 主要思路 优点
表达式求值 先将中缀表达式转换为后缀表达式,再对后缀表达式进行求值,过程中使用栈来存储运算符和操作数 简化了表达式的计算过程,避免了括号优先级的复杂判断
括号匹配 利用栈存储左括号,遇到右括号时与栈顶左括号进行匹配 简单高效,能够快速判断括号是否成对出现

栈作为一种简单而强大的数据结构,在表达式求值和括号匹配等场景中发挥了重要作用。通过合理运用栈的后进先出特性,我们可以解决许多实际问题,提高算法的效率和代码的可读性。希望本文能帮助你更好地理解栈的应用,在编程实践中灵活运用这一数据结构。

栈 - 栈的应用 - 表达式求值、括号匹配等