当前位置: 首页 > news >正文

Rust 数据结构与算法:5栈:用栈实现前缀、中缀、后缀表达式

3、前缀、中缀和后缀表达式

计算机是从左到右处理数据的,类似(A + (B * C))这样的完全括号表达式,计算机如何跳到内部括号计算乘法,然后跳到外部括号计算加法呢?

一种直观的方法是将运算符移到操作数外,分离运算符和操作数。计算时先取运算符再取操作数,计算结果则作为当前值参与后面的运算,直到完成对整个表达式的计算。

可将中缀表达式A + B中的“+”移出来,既可以放前面,也可以放后面,得到的将是+ A B和A B +。这两种不同的表达式分别被称为前缀表达式和后缀表达式。

前缀表达式要求所有运算符在处理的两个操作数之前,后缀表达式则要求所有运算符在相应的操作数之后。

在这里插入图片描述

前缀、中缀和后缀表达式

在这里插入图片描述

将中缀表达式转换为前缀和后缀表达式

这里规定:运算符只有+ - * /,操作数则被定义为任何大写字母A~Z或数字0~9。

代码如下:

#[derive(Debug)]
struct Stack<T> {size: usize,  // 栈大小data: Vec<T>, // 栈数据
}impl<T> Stack<T> {// 初始化空栈fn new() -> Self {Self {size: 0,data: Vec::new(), // 以 Vec 为低层}}fn is_empty(&self) -> bool {0 == self.size}fn len(&self) -> usize {self.size}// 清空栈fn clear(&mut self) {self.size = 0;self.data.clear();}// 将数据保存在 Vec 的末尾fn push(&mut self, val: T) {self.data.push(val);self.size += 1;}// 将栈顶减 1 后,弹出数据fn pop(&mut self) -> Option<T> {if 0 == self.size {return None;};self.size -= 1;self.data.pop()}// 返回栈顶数据引用和可变引用fn peek(&self) -> Option<&T> {if 0 == self.size {return None;}self.data.get(self.size - 1)}fn peek_mut(&mut self) -> Option<&mut T> {if 0 == self.size {return None;}self.data.get_mut(self.size - 1)}// 以下是为栈实现的迭代功能// into_iter: 栈改变,成为迭代器// iter: 栈不变,得到不可变迭代器// iter_mut:栈不变,得到可变迭代器fn into_iter(self) -> IntoIter<T> {// into_iter()方法获取了一个迭代器,然后进行迭代IntoIter(self)}fn iter(&self) -> Iter<T> {let mut iterator = Iter { stack: Vec::new() };for item in self.data.iter() {iterator.stack.push(item);}iterator}fn iter_mut(&mut self) -> IterMut<T> {let mut iterator = IterMut { stack: Vec::new() };for item in self.data.iter_mut() {iterator.stack.push(item);}iterator}
}
// 实现三种迭代功能
struct IntoIter<T>(Stack<T>);
// Iterator 是 Rust 的迭代器 迭代器(iterator)负责遍历序列中的每一项和决定序列何时结束的逻辑。当使用迭代器时,我们无需重新实现这些逻辑。
impl<T: Clone> Iterator for IntoIter<T> {// into_iter()方法获取了一个迭代器,然后进行迭代。type Item = T;fn next(&mut self) -> Option<Self::Item> {// 迭代器之所以成为迭代器,是因为实现了Iterator trait。要实现该特征,最主要的就是实现其中的 next 方法,该方法控制如何从集合中取值,最终返回值的类型是关联类型 Item。if !self.0.is_empty() {self.0.size -= 1;self.0.data.pop()} else {None}}
}
// 'a 生命周期标识 用于帮助编译器检查引用的有效性,避免悬垂引用和使用已被释放的内存。
// 从所有权的角度来理解,就是它可以避免因为Copy或者clone的造成的不必要开销
struct Iter<'a, T: 'a> {stack: Vec<&'a T>, // 'a 被用在了传参类型 T 上
}
impl<'a, T> Iterator for Iter<'a, T> {type Item = &'a T; // 生命周期标识只作用于引用上,且放在&符号之后 如这里的 &'a Tfn next(&mut self) -> Option<Self::Item> {self.stack.pop()}
}struct IterMut<'a, T: 'a> {stack: Vec<&'a mut T>,
}
impl<'a, T> Iterator for IterMut<'a, T> {type Item = &'a mut T;fn next(&mut self) -> Option<Self::Item> {self.stack.pop()}
}use std::collections::HashMap;fn main() {let infix = "( A + B ) * ( C + D )";let postfix = infix_to_postfix(infix);match postfix {Some(val) => {println!("{infix} -> {val}");},None => {println!("{infix} is not a correct infix string");}}let infix = "( 1 + 2 ) * 3";let postfix = infix_to_postfix(infix);match postfix {Some(val) => {println!("{infix} -> {val}");},None => {println!("{infix} is not a correct infix string");}}
}// 中缀转后缀
fn infix_to_postfix(infix:&str) -> Option<String> {// 括号匹配检验if !par_checker3(infix) { return None;};// 设置各个运算符的优先级let mut prec = HashMap::new();//  HashMap<K, V> 类型储存了一个键类型 K 对应一个值类型 V 的映射。它通过一个 哈希函数(hashing function)来实现映射,决定如何将键和值放入内存中。prec.insert("(", 1);prec.insert(")", 1);prec.insert("+", 2);prec.insert("-", 2);prec.insert("*", 3);prec.insert("/", 3);// ops 用于保存运算符, postfix 用于保存后缀表达式let mut ops = Stack::new(); // 保存运算符let mut postfix = Vec::new(); // 保存后缀表达式for token in infix.split_whitespace() { // 按空格分割字符串切片// 将数字 0 - 9 和 大写字母 A - Z 入栈if ("A" <= token && token <= "Z") || ("0" <= token && token <= "9") {postfix.push(token); // 如果是字符或数字就存入 postfix}else if "(" == token {// 遇到开始符号,将运算符入栈ops.push(token); // 如果是开始括号运算符 ( 就存储 ops}else if ")" == token {// 比如 infix 为 (1 + 2) * 3,此时 postfix 类似为 [1,2],ops 类似为 [(,+]  // 遇到结束符号,将操作数入栈let mut top = ops.pop().unwrap(); // 如果是结束括号运算符 就去 ops 中匹配 对应的 开始 括号运算符 此时 ops 类似为 [(],因为 + 已经被 pop 出去了while top !="(" { // 如果匹配到的不是 ( 开始括号运算符,则说明是 + - * / 运算符 此时 top 比如为 +postfix.push(top); // 将 + - * / 运算符入栈至 postfix,比如 infix 为 (1 + 2) * 3,此时 postfix 类似为 [1,2,+]top = ops.pop().unwrap(); // 此时 ops 类似为 [],top = "(",到这里 while 就结束了 因为 top = "("}// 到这里时 ops 类似为 [],infix 为 (1 + 2) * 3 中的 () 就被丢弃了,而 postfix 已经为,如:[1,2,+]}else {// + - * / 运算符// 比较运算符的优先级以决定是否将运算符添加到 postfix 列表中while (!ops.is_empty()) && (prec[ops.peek().unwrap()] >= prec[token]) {postfix.push(ops.pop().unwrap()); // 比如 peek 是 *,而 当前 token 是 + ,则直接把 * 放入 postfix }ops.push(token); // 此时将 * push 了进去// 此时 ops 如 [*]}}// 比如:infix 为 (1 + 2) * 3//  此时 ops 如 [*]// 此时 postfix 如 [1,2,+,3]// 将剩下的操作数入栈while !ops.is_empty() {postfix.push(ops.pop().unwrap()); // 此时 postfix 如 [1,2,+,3,*]}// 出栈并组成字符串let mut postfix_str = "".to_string();for c in postfix {postfix_str += &c.to_string();postfix_str += " "; // 加上空格}Some(postfix_str)
}// 同时检测多种开始符号和结束符号是否匹配
fn par_match(open: char, close: char) -> bool {let opens = "([{";let closers = ")]}";opens.find(open) == closers.find(close)
}
// 基于栈的符号匹配
fn par_checker3(par: &str) -> bool {let mut char_list = Vec::new();for c in par.chars() {char_list.push(c);}let mut index = 0;let mut balance = true;let mut stack = Stack::new();while index < char_list.len() && balance {let c = char_list[index];// 将开始符号入栈if '(' == c || '[' == c || '{' == c {stack.push(c);}// 如果是结束符号,则判断是否平衡if ')' == c || ']' == c || '}' == c {if stack.is_empty() {balance = false;} else {let top = stack.pop().unwrap();if !par_match(top, c) {balance = false;}}}// 非括号字符直接跳过index += 1;}balance && stack.is_empty()
}

运行结果:

在这里插入图片描述

相关文章:

  • 代码随想录训练营第三十期|第三十二天|贪心算法 part02|● 122.买卖股票的最佳时机II ● 55. 跳跃游戏 ● 45.跳跃游戏II
  • Spring GateWay
  • 【从零开始学设计模式】第七章_适配器模式
  • 《剑指offer》
  • 基于HTML5实现动态烟花秀效果(含音效和文字)实战
  • Netty应用(一) 之 NIO概念 基本编程
  • 2.15题目
  • 抽象的前端
  • 初识webpack(二)解析resolve、插件plugins、dev-server
  • 【AI视野·今日NLP 自然语言处理论文速览 第七十九期】Thu, 18 Jan 2024
  • java.lang.NoClassDefFoundError: org/springframework/core/GenericTypeResolver
  • mongodb 导出数据
  • 【并发编程】AQS原理
  • LCP 30. 魔塔游戏
  • Bean 的六种作用域
  • __proto__ 和 prototype的关系
  • Angular6错误 Service: No provider for Renderer2
  • Hibernate【inverse和cascade属性】知识要点
  • JSDuck 与 AngularJS 融合技巧
  • PAT A1092
  • python大佬养成计划----difflib模块
  • tweak 支持第三方库
  • Wamp集成环境 添加PHP的新版本
  • win10下安装mysql5.7
  • 从PHP迁移至Golang - 基础篇
  • 名企6年Java程序员的工作总结,写给在迷茫中的你!
  • 入职第二天:使用koa搭建node server是种怎样的体验
  • 使用 Xcode 的 Target 区分开发和生产环境
  • 哈罗单车融资几十亿元,蚂蚁金服与春华资本加持 ...
  • 移动端高清、多屏适配方案
  • ## 临床数据 两两比较 加显著性boxplot加显著性
  • (4)logging(日志模块)
  • (poj1.2.1)1970(筛选法模拟)
  • (二)fiber的基本认识
  • (附源码)ssm教师工作量核算统计系统 毕业设计 162307
  • (六)Hibernate的二级缓存
  • (切换多语言)vantUI+vue-i18n进行国际化配置及新增没有的语言包
  • (未解决)jmeter报错之“请在微信客户端打开链接”
  • (小白学Java)Java简介和基本配置
  • (转)PlayerPrefs在Windows下存到哪里去了?
  • (转)利用ant在Mac 下自动化打包签名Android程序
  • * CIL library *(* CIL module *) : error LNK2005: _DllMain@12 already defined in mfcs120u.lib(dllmodu
  • .aanva
  • .gitignore文件_Git:.gitignore
  • .net core Swagger 过滤部分Api
  • .net 按比例显示图片的缩略图
  • .Net 中Partitioner static与dynamic的性能对比
  • .NET/C# 将一个命令行参数字符串转换为命令行参数数组 args
  • .NET设计模式(7):创建型模式专题总结(Creational Pattern)
  • .ui文件相关
  • /*在DataTable中更新、删除数据*/
  • @Tag和@Operation标签失效问题。SpringDoc 2.2.0(OpenApi 3)和Spring Boot 3.1.1集成
  • [ element-ui:table ] 设置table中某些行数据禁止被选中,通过selectable 定义方法解决
  • [AIR] NativeExtension在IOS下的开发实例 --- IOS项目的创建 (一)
  • [Android开源]EasySharedPreferences:优雅的进行SharedPreferences数据存储操作