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()
}

运行结果:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/470465.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

steam搬砖项目赚钱吗?3年亲身经历告诉你真相

csgo饰品交易市场由来已久&#xff0c;且玩家众多&#xff0c;绝对蓝海。而steam搬砖项目就是基于csgo游戏的交易。在steam用美金买入道具、装备和饰品&#xff0c;然后搬砖到国内的网易buff售卖&#xff0c;从中赚取一定的差价和汇率差额。 steam搬砖怎么赚钱&#xff1f;在s…

2019年通信工程师初级 实务 真题

文章目录 一、第9章 通信动力与环境通信电源系统的主要功能&#xff1a;“供”、“配”、“储”、“发”、“变” 二、第2章 传输网三、第3章 接入网四、第4章 互联网 一、第9章 通信动力与环境 【问题一】 网络通信设备对动力与环境的质量要求可以归纳为 &#xff08;1&#…

模拟电子技术——基本放大电路

文章目录 前言一、三极管输入输出特性三极管放大作用三极管电流放大关系三极管的特性曲线 二、基本放大电路-电路结构与工作原理基本放大电路的构成基本放大电路放大原理三种基本放大电路比较 三、基本放大电路静态工作点什么是静态工作点&#xff1f;静态工作点的作用估算法分…

多模态学习综述(MultiModal Learning)

最早开始关注到多模态机器学习是看到Jeff Dean在2019年年底NeurIPS大会上的一个采访报道&#xff0c;讲到了2020年机器学习趋势&#xff1a;多任务和多模态学习将成为突破口。 Jeff Dean 谈2020年机器学习趋势&#xff1a;多任务和多模式学习将成为突破口 站在2022年&#xff…

【Java】零基础蓝桥杯算法学习——二分查找

算法模板一: // 数组arr的区间[0,left-1]满足arr[i]<k,[left,n-1]满足arr[i]>k;Scanner scan new Scanner(System.in);int[] arr {1,2,3,4,5};int left 0,right arr.length-1;int k scan.nextInt();while(left<right) {//leftright时退出循环int mid (leftrigh…

揭秘Angular世界的奥秘:全面提升你的前端开发技能!

介绍&#xff1a;Angular是一个由Google维护的开源JavaScript框架&#xff0c;专为构建Web应用程序而设计&#xff0c;特别适合开发大型单页应用&#xff08;SPA&#xff09;。以下是对Angular的详细介绍&#xff1a; 技术栈&#xff1a;Angular使用HTML作为模板语言&#xff0…

【Java多线程案例】定时器

1. 定时器简介 定时器&#xff1a;想必大家一定对定时器这个概念不陌生&#xff01;因为它经常出现在我们的日常生活和编程学习中&#xff0c;定时器就好比是一个"闹钟"&#xff0c;会在指定时间处理某件事&#xff08;例如响铃&#xff09;&#xff0c;而在编程世界…

单html页面使用Vue3和Element-Plus

一、快速入门 使用CDN方式引入Vue3使用CDN方式引入Element-Plus的样式文件和组件库 案例 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, ini…

随机过程及应用学习笔记(四) 马尔可夫过程

马尔可夫过程是理论上和实际应用中都十分重要的一类随机过程。 目录 前言 一、马尔可夫过程的概念 二、离散参数马氏链 1 定义 2 齐次马尔可夫链 3 齐次马尔可夫链的性质 三、齐次马尔可夫链状态的分类 四、有限马尔可夫链 五、状态的周期性 六、极限定理 七、生灭过…

如何将字体添加到 ONLYOFFICE 文档服务器 8.0

作者&#xff1a;VincentYoung 阅读本文&#xff0c;了解如何为自己的在线办公软件 ONLYOFFICE 文档服务器的字体库添加字体 ONLYOFFICE 文档是什么 ONLYOFFICE 文档是一个功能强大的文档编辑器&#xff0c;支持处理文本文档、电子表格、演示文稿、可填写表单、PDF 和电子书…

设计模式巡礼:多板适配案例解析与深度重构

theme: cyanosis 月黑风高&#xff0c;好兄弟发给我一个重构需求&#xff0c;咨询我的意见。 一、 场景分析 开发的产品是需要运行到不同的定制Android板子&#xff0c;不同板子有对应的不同SDK提供的API&#xff0c;目前的业务端&#xff0c;业务流程基本是确定的&#xff0…

统一数据返回格式 及 可能遇到的问题;统一异常处理

统一数据返回格式 统一数据返回格式就像我们寄快递一样&#xff0c;不管你需要寄的东西具体是什么都需要将它打包到统一的快递箱中。 此时我们需要一个“快递箱”用来将返回的数据“装”在里面。这个类是根据业务情况来自行定义的。 Data public class Resp<T> {//200…