【数据结构(邓俊辉)学习笔记】栈与队列01——栈应用(栈混洗、前缀后缀表达式、括号匹配)

文章目录

  • 0. 概述
  • 1. 操作与接口
  • 2. 操作实例
  • 3. 实现
  • 4. 栈与递归
  • 5. 应用
    • 5.1 逆序输出
      • 5.1.1 进制转换
        • 5.1.1.1 思路
        • 5.1.1.2 算法实现
    • 5.2 递归嵌套
      • 5.2.1 栈混洗
        • 5.2.1.1 混洗
        • 5.2.1.2 计数
        • 5.2.1.3 甄别
      • 5.2.2 括号匹配
        • 5.2.2.1 构思
        • 5.2.2.2 实现
        • 5.2.2.3 实例
    • 5.3 延迟缓冲
      • 5.3.1 中缀表达式
        • 5.3.1.1 表达式求值
        • 5.3.1.2 优先级表
        • 5.3.1.3 求值算法
      • 5.3.2 逆波兰表达式(后缀表达式)
        • 5.3.2.1 RPN
        • 5.3.2.2 RPN实例
        • 5.3.2.3 infix 到postfix 转换

0. 概述

介绍下栈的接口与应用。

1. 操作与接口

在这里插入图片描述

2. 操作实例

在这里插入图片描述

3. 实现

在这里插入图片描述
基于向量

#include "Vector/Vector.h" //以向量为基类,派生出栈模板类
template <typename T> 
class Stack: public Vector<T> { //将向量的首/末端作为栈底/顶
public: //原有接口一概沿用void push ( T const& e ) { insert ( e ); } //入栈:等效于将新元素作为向量的末元素插入T pop() { return remove ( size() - 1 ); } //出栈:等效于删除向量的末元素T& top() { return ( *this ) [size() - 1]; } //取顶:直接返回向量的末元素
};

基于列表

#include "List/List.h" //以列表为基类,派生出栈模板类
template <typename T> 
class Stack: public List<T> { //将列表的首/末端作为栈底/顶
public: //原有接口一概沿用void push ( T const& e ) { insertAsLast ( e ); } //入栈:等效于将新元素作为列表的末元素插入T pop() { return remove ( last() ); } //出栈:等效于删除列表的末元素T& top() { return last()->data; } //取顶:直接返回列表的末元素
};

4. 栈与递归

在这里插入图片描述

5. 应用

典型应用场合
在这里插入图片描述

5.1 逆序输出

5.1.1 进制转换

5.1.1.1 思路

在这里插入图片描述
在这里插入图片描述

5.1.1.2 算法实现
  1. 递归实现
void convert ( Stack<char>& S, __int64 n, int base ) { //十进制数n到base进制的转换(递归版)static char digit[] //0 < n, 1 < base <= 16,新进制下的数位符号,可视base取值范围适当扩充= { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };if ( 0 < n ) { //在尚有余数之前,不断convert ( S, n / base, base ); //通过递归得到所有更高位S.push ( digit[n % base] ); //输出低位}
} //新进制下由高到低的各数位,自顶而下保存于栈S中
  1. 迭代实现

优化点:
这里的静态数位符号表在全局只需保留一份,但与一般的递归函数一样,该函数在递归调用栈中的每一帧都仍需记录参数S、n和base。将它们改为全局变量固然可以节省这部分空间,但依然不能彻底地避免因调用栈操作而导致的空间和时间消耗。

改写成迭代版将空间消耗降至O(1)。

void convert ( Stack<char>& S, __int64 n, int base ) { //十进制数n到base进制的转换(迭代版)static char digit[] //0 < n, 1 < base <= 16,新进制下的数位符号,可视base取值范围适当扩充= { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };while ( n > 0 ) { //由低到高,逐一计算出新进制下的各数位int remainder = ( int ) ( n % base ); S.push ( digit[remainder] ); //余数(当前位)入栈n /= base; //n更新为其对base的除商}
} //新进制下由高到低的各数位,自顶而下保存于栈S中

5.2 递归嵌套

5.2.1 栈混洗

5.2.1.1 混洗

栈混洗就是按照某种约定的规则对栈中元素进行重新的排列。
在这里插入图片描述
上图采用约定尖括号<表示栈顶,方括号]表示栈底。

在遵守以上规则的前提下,同一输入序列完全可以导出不同的栈混洗序列。
在这里插入图片描述
n个元素的栈混洗总数不会超过全排列n!。

一般地对于长度为n的输入序列,每一栈混洗都对应于由栈S的n次push和n次pop构成的某一合法操作序列比如[ 3, 2,4, 1> 
即对应于操作序列: {push, push, push, pop, pop, push, pop, pop } 
反之,由n次push和n次pop构成的任何操作序列,只要满足“任一前缀中的push不少于pop”这一限制,则该序列也必然对应于
某个栈混洗。
5.2.1.2 计数

在这里插入图片描述
假定输入栈A中共有n个元素,自顶向下依次编号为1,2,3 … n。将注意力放在第一个元素1上,它将被首次推入中转栈S中,若1号元素被弹出,则栈S就是空栈,此时在栈B中包括刚推入的1号元素,累计共有k个元素。那么栈A中就应该还留存有最后的n-k个元素。此时B中k个元素和A中n-k个元素它们的栈混洗是相互独立的,故1号元素作为第k个元素被推入B中的情况,累计栈混洗总数如下
在这里插入图片描述

5.2.1.3 甄别

在这里插入图片描述
在这里插入图片描述

5.2.2 括号匹配

5.2.2.1 构思

在这里插入图片描述

5.2.2.2 实现

算法思想:只要将push、pop操作分别与左、右括号相对应,则长度为n的栈混洗,必然与由n对括号组成的合法表达式彼此对应。 比如,栈混洗[ 3, 2, 4, 1 >对应于表达式"( ( ( ) ) ( ) )"。按照这一理解,借助栈结构,只需扫描一趟表达式,即可在线性时间内,判定其中的括号是否匹配。

bool paren ( const char exp[], int lo, int hi ) { //表达式括号匹配检查,可兼顾三种括号Stack<char> S; //使用栈记录已发现但尚未匹配的左括号for ( int i = lo; i <= hi; i++ ) /* 逐一检查当前字符 */switch ( exp[i] ) { //左括号直接进栈;右括号若与栈顶失配,则表达式必不匹配case '(': case '[': case '{': S.push ( exp[i] ); break;case ')': if ( ( S.empty() ) || ( '(' != S.pop() ) ) return false; break;case ']': if ( ( S.empty() ) || ( '[' != S.pop() ) ) return false; break;case '}': if ( ( S.empty() ) || ( '{' != S.pop() ) ) return false; break;default: break; //非括号字符一律忽略}return S.empty(); //整个表达式扫描过后,栈中若仍残留(左)括号,则不匹配;否则(栈空)匹配
}
5.2.2.3 实例

在这里插入图片描述

5.3 延迟缓冲

在一些应用问题中,输入可分解为多个单元并通过迭代依次扫描处理,但过程中的各步计算往往滞后于扫描的进度,需要待到必要的信息已完整到一定程度之后,才能作出判断并实施计算。在这类场合,栈结构则可以扮演数据缓冲区的角色。

中缀表达式算法的求解过程同逆波兰表达式算法,下面介绍下。

5.3.1 中缀表达式

5.3.1.1 表达式求值

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.3.1.2 优先级表

将不同运算符之间的运算优先级关系,描述为一张二维表格。
在这里插入图片描述

#define N_OPTR 9 //运算符总数
typedef enum { ADD, SUB, MUL, DIV, POW, FAC, L_P, R_P, EOE } Operator; //运算符集合
//加、减、乘、除、乘方、阶乘、左括号、右括号、起始符与终止符const char pri[N_OPTR][N_OPTR] = { //运算符优先等级 [栈顶] [当前]/*              |-------------------- 当 前 运 算 符 --------------------| *//*              +      -      *      /      ^      !      (      )      \0 *//* --  + */    '>',   '>',   '<',   '<',   '<',   '<',   '<',   '>',   '>',/* |   - */    '>',   '>',   '<',   '<',   '<',   '<',   '<',   '>',   '>',/* 栈  * */    '>',   '>',   '>',   '>',   '<',   '<',   '<',   '>',   '>',/* 顶  / */    '>',   '>',   '>',   '>',   '<',   '<',   '<',   '>',   '>',/* 运  ^ */    '>',   '>',   '>',   '>',   '>',   '<',   '<',   '>',   '>',/* 算  ! */    '>',   '>',   '>',   '>',   '>',   '>',   ' ',   '>',   '>',/* 符  ( */    '<',   '<',   '<',   '<',   '<',   '<',   '<',   '=',   ' ',/* |   ) */    ' ',   ' ',   ' ',   ' ',   ' ',   ' ',   ' ',   ' ',   ' ',/* -- \0 */    '<',   '<',   '<',   '<',   '<',   '<',   '<',   ' ',   '='
};
5.3.1.3 求值算法
double evaluate ( char* S, char* RPN ) { //对(已剔除白空格的)表达式S求值,并转换为逆波兰式RPNStack<double> opnd; Stack<char> optr; //运算数栈、运算符栈 /*DSA*/任何时刻,其中每对相邻元素之间均大小一致/*DSA*/ char* expr = S;optr.push ( '\0' ); //尾哨兵'\0'也作为头哨兵首先入栈while ( !optr.empty() ) { //在运算符栈非空之前,逐个处理表达式中各字符if ( isdigit ( *S ) ) { //若当前字符为操作数,则readNumber ( S, opnd ); append ( RPN, opnd.top() ); //读入操作数,并将其接至RPN末尾} else //若当前字符为运算符,则switch ( priority ( optr.top(), *S ) ) { //视其与栈顶运算符之间优先级高低分别处理case '<': //栈顶运算符优先级更低时optr.push ( *S ); S++; //计算推迟,当前运算符进栈break;case '=': //优先级相等(当前运算符为右括号或者尾部哨兵'\0')时optr.pop(); S++; //脱括号并接收下一个字符break;case '>': { //栈顶运算符优先级更高时,可实施相应的计算,并将结果重新入栈char op = optr.pop(); append ( RPN, op ); //栈顶运算符出栈并续接至RPN末尾if ( '!' == op ) //若属于一元运算符opnd.push ( calcu ( op, opnd.pop() ) ); //则取一个操作数,计算结果入栈else { //对于其它(二元)运算符double opnd2 = opnd.pop(), opnd1 = opnd.pop(); //取出后、前操作数 /*DSA*/提问:可否省去两个临时变量?opnd.push ( calcu ( opnd1, op, opnd2 ) ); //实施二元计算,结果入栈}break;}default : exit ( -1 ); //逢语法错误,不做处理直接退出}//switch/*DSA*/displayProgress ( expr, S, opnd, optr, RPN );}//whilereturn opnd.pop(); //弹出并返回最后的计算结果
}

算法思想:
~~~~~~~        该算法自左向右扫描表达式,并对其中字符逐一做相应的处理。那些已经扫描过但(因信息不足)尚不能处理的操作数与运算符,将分别缓冲至栈opnd和栈optr。一旦判定已缓存的子表达式优先级足够高,便弹出相关的操作数和运算符,随即执行运算,并将结果压入栈opnd。
~~~~~~~        请留意这里区分操作数和运算符的技巧。一旦当前字符由非数字转为数字,则意味着开始进入一个对应于操作数的子串范围。由于这里允许操作数含有多个数位,甚至可能是小数。

void readNumber ( char*& p, Stack<double>& stk ) { //将起始于p的子串解析为数值,并存入操作数栈stk.push ( ( double ) ( *p - '0' ) ); //当前数位对应的数值进栈while ( isdigit ( * ( ++p ) ) ) //若有后续数字(多位整数),则stk.push ( stk.pop() * 10 + ( *p - '0' ) ); //追加之(可能上溢)if ( '.' == *p ) { //若还有小数部分double fraction = 1; //则while ( isdigit ( * ( ++p ) ) ) //逐位stk.push ( stk.pop() + ( *p - '0' ) * ( fraction /= 10 ) ); //加入(可能下溢)}
}

根据当前字符及其后续的若干字符,利用另一个栈解析出当前的操作数。解析完毕,当前字符将再次聚焦于一个非数字字符。

不同优先级的处置如下:
调用priority ()函数,将其与栈optr的栈顶操作符做一比较之后,即可视二者的优先级高低,分三种情况相应地处置。

Operator optr2rank ( char op ) { //由运算符转译出编号switch ( op ) {case '+' : return ADD; //加case '-' : return SUB; //减case '*' : return MUL; //乘case '/' : return DIV; //除case '^' : return POW; //乘方case '!' : return FAC; //阶乘case '(' : return L_P; //左括号case ')' : return R_P; //右括号case '\0': return EOE; //起始符与终止符default  : exit ( -1 ); //未知运算符}
}char priority ( char op1, char op2 ) //比较两个运算符之间的优先级
{ return pri[optr2rank ( op1 ) ][optr2rank ( op2 ) ]; }

将其与栈optr的栈顶操作符做一比较之后,即可视二者的优先级高低,分三种情况相应地处置。

1)若当前运算符的优先级更高,则optr中的栈顶运算符尚不能执行
2)反之,一旦栈顶运算符的优先级更高,则可以立即弹出并执行对应的运算一目运算符!弹出一个数,双目运算符弹出两个数。
3)当前运算符与栈顶运算符的优先级“相等”对右括号的上述处理方式,将在optr栈顶出现操作符'('时终止。除左、右括号外,还有一种优先级相等的合法情况,即pri['\0']['\0'] = '='

5.3.2 逆波兰表达式(后缀表达式)

5.3.2.1 RPN

在这里插入图片描述
在这里插入图片描述

5.3.2.2 RPN实例

在这里插入图片描述

5.3.2.3 infix 到postfix 转换

在这里插入图片描述
在这里插入图片描述
上述evaluate()算法在对表达式求值的同时,也顺便完成了从常规表达式到RPN表达式的转换。

该算法借助append()函数将各操作数和运算符适时地追加至串rpn的末尾,直至得到完整的RPN表达式。

void append ( char* rpn, double opnd ) { //将操作数接至RPN末尾char buf[64];if ( ( int ) opnd < opnd ) sprintf ( buf, "%6.2f \0", opnd ); //浮点格式,或else                       sprintf ( buf, "%d \0", ( int ) opnd ); //整数格式strcat ( rpn, buf ); //RPN加长
}void append ( char* rpn, char optr ) { //将运算符接至RPN末尾int n = strlen ( rpn ); //RPN当前长度(以'\0'结尾,长度n + 1)sprintf ( rpn + n, "%c \0", optr ); //接入指定的运算符
}

这里,在接入每一个新的操作数或操作符之前,都要调用realloc()函数以动态地扩充RPN表达式的容量,因此会在一定程度上影响时间效率。
在十分注重这方面性能的场合,读者可以做适当的改进——比如,有必要扩容时即令容量加倍。

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

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

相关文章

(✌)粤嵌—2024/5/9—寻找两个正序数组的中位数

代码实现&#xff1a; int binary_search(int *arr, int n, int key) {int head 0, tail n - 1, mid;while (head < tail) {mid (head tail) / 2;if (arr[mid] key) {return mid;}if (arr[mid] > key) {tail mid - 1;} else {head mid 1;}}return head; }void in…

JetBrains的Java集成开发环境IntelliJ 2024.1版本在Windows/Linux系统的下载与安装配置

目录 前言一、IntelliJ在Windows安装二、IntelliJ在Linux安装三、Windows下使用配置四、Linux下使用配置总结 前言 ​ “ IntelliJ IDEA Ultimate是一款功能强大的Java集成开发环境&#xff08;IDE&#xff09;。它提供了丰富的功能和工具&#xff0c;可以帮助开发人员更高效地…

1067 试密码(测试点2测试点5)

solution 测试点2,5 : The test may have space,so you should use getline() function but not cin() function #include<iostream> #include<string> using namespace std; int main(){string ans, test;int n, cnt 0;cin >> ans >> n;getchar();…

基于 C# 开源的 EF Core 查询计划可视化神器

介绍 EFCore.Visualizer 是 Entity Framework Core 查询计划调试器&#xff0c;一个开源的 EF Core 查询计划可视化工具, 您可以直接在 Visual Studio 中查看查询的查询计划&#xff0c;开箱即用&#xff0c;非常方便。目前&#xff0c;可视化工具支持 SQL Server 和 PostgreS…

java后端15问!

前言 最近一位粉丝去面试一个中厂&#xff0c;Java后端。他说&#xff0c;好几道题答不上来&#xff0c;于是我帮忙整理了一波答案 G1收集器JVM内存划分对象进入老年代标志你在项目中用到的是哪种收集器&#xff0c;怎么调优的new对象的内存分布局部变量的内存分布Synchroniz…

笨方法学习python(七)

输入 一般软件做的事情主要就是下面几条&#xff1a; 接受人的输入。改变输入。打印出改变了的输入。 前面几节都是print输出&#xff0c;这节了解一下输入input&#xff1b;在python2中使用的是raw_input&#xff0c;python3就只是input。 print ("How old are you?&…

springboot如何查看版本号之间的相互依赖

第一种&#xff1a; 查看本地项目maven的依赖&#xff1a; ctrl鼠标左键&#xff1a;按下去可以进入maven的下一层&#xff1a; ctrl鼠标左键&#xff1a;按下去可以进入maven的再下一层&#xff1a; 就可以查看springboot的一些依赖版本号了&#xff1b; 第二种&#xff1a; 还…

RuoYi-Vue-Plus (Echarts 图表)

一、echarts 图表介绍和使用 官网地址:目前echarts以及贡献给Apache Apache EChartshttps://echarts.apache.org/zh/index.htmlecharts配置项手册 Documentation - Apache EChartshttps://echarts.apache.org/z

【快捷部署】022_ZooKeeper(3.5.8)

&#x1f4e3;【快捷部署系列】022期信息 编号选型版本操作系统部署形式部署模式复检时间022ZooKeeper3.5.8Ubuntu 20.04tar包单机2024-05-07 一、快捷部署 #!/bin/bash ################################################################################# # 作者&#xff…

宏的优缺点?C++有哪些技术替代宏?(const)权限的平移、缩小

宏的优缺点&#xff1f; 优点&#xff1a; 1.增强代码的复用性。【减少冗余代码】 2.提高性能&#xff0c;提升代码运行效率。 缺点&#xff1a; 1.不方便调试宏。&#xff08;因为预编译阶段进行了替换&#xff09; 2.导致代码可读性差&#xff0c;可维护性差&#xff0…

OpenSSL实现AES的ECB和CBC加解密,可一次性加解密任意长度的明文字符串或字节流(QT C++环境)

本篇博文讲述如何在Qt C的环境中使用OpenSSL实现AES-ECB/CBC-Pkcs7加/解密&#xff0c;可以一次性加解密一个任意长度的明文字符串或者字节流&#xff0c;但不适合分段读取加解密的&#xff08;例如&#xff0c;一个4GB的大型文件需要加解密&#xff0c;要分段读取&#xff0c;…

基于无监督学习算法的滑坡易发性评价的实施(k聚类、谱聚类、Hier聚类)

基于无监督学习算法的滑坡易发性评价的实施 1. k均值聚类2. 谱聚类3. Hier聚类4. 基于上述聚类方法的易发性实施本研究中的数据集和代码可从以下链接下载: 数据集实施代码1. k均值聚类 K-Means 聚类是一种矢量量化方法,最初来自信号处理,旨在将 N 个观测值划分为 K 个聚类,…

我悟了!24年软考架构就这100道母题,历史重复率90%

距离软考考试的时间越来越近了&#xff0c;趁着这两周赶紧准备起来 今天给大家整理了——系统架构设计师100道经典母题&#xff0c;有PDF&#xff0c;可打印&#xff0c;每天刷几道。 一、计算机系统基础&#xff08;12&#xff09; 1. 计算机采用分级存储体系的主要目的是为了…

深度学习笔记001

目录 一、批量规范化 二、残差网络ResNet 三、稠密连接网络&#xff08;DenseNet&#xff09; 四、循环神经网络 五、信息论 六、梯度截断 本篇blog仅仅是本人在学习《动手学深度学习 Pytorch版》一书中做的一些笔记&#xff0c;感兴趣的读者可以去官网http://zh.gluon.a…

中小学校活动向媒体投稿报道宣传有哪些好方法

作为一所中小学校的教师,我肩负着向外界展示学校风采、宣传校园文化活动的重要使命。起初,每当学校举办特色活动或取得教学成果时,我都会满怀热情地撰写新闻稿,希望通过媒体的平台让更多人了解我们的故事。然而,理想丰满,现实骨感,我很快发现,通过电子邮件向媒体投稿的过程充满…

技术速递|Python in Visual Studio Code 2024年4月发布

排版&#xff1a;Alan Wang 我们很高兴地宣布 Visual Studio Code 的 Python 和 Jupyter 扩展 2024 年 4 月发布&#xff01; 此版本包括以下公告&#xff1a; 改进了 Flask 和 Django 的调试配置流程Jupyter Run Dependent Cells with Pylance 的模块和导入分析Hatch 环境发…

Vue+OpenLayers7入门到实战:OpenLayers解析通过fetch请求的GeoJson格式数据,并叠加要素文字标注,以行政区划边界为例

返回《Vue+OpenLayers7》专栏目录:Vue+OpenLayers7入门到实战 前言 本章介绍如何使用OpenLayers7在地图上通过fetch请求geojson数据,然后通过OpenLayers解析为Feature要素叠加到图层上,并且通过动态设置标注方式显示要素属性为文字标注。 本章还是以行政区划边界为例,这个…

网工常用工具——Xshell

今天给各位介绍一下&#xff0c;Xshell工具 Xshell是一款功能强大的终端模拟器&#xff0c;主要用于Windows操作系统&#xff0c;用于远程访问和管理服务器&#xff0c;允许用户通过SSH&#xff08;Secure Shell&#xff09;协议安全地连接到远程Linux/Unix服务器或其他支持SS…

Linux的并发与竞争

文章目录 一、并发二、竞争三、保护内容是什么四、解决并发与竞争的几种常用方法1.原子操作原子整型API函数原子位操作 API 函数 2.自旋锁自旋锁格式如下&#xff1a;自旋锁 API 函数自旋锁的使用注意事项 3.信号量信号量 API 函数信号量格式如下&#xff1a; 4.互斥体API函数如…

【LeetCode刷题记录】994. 腐烂的橘子

994 腐烂的橘子 在给定的 m x n 网格 grid 中&#xff0c;每个单元格可以有以下三个值之一&#xff1a; 值 0 代表空单元格&#xff1b; 值 1 代表新鲜橘子&#xff1b; 值 2 代表腐烂的橘子。 每分钟&#xff0c;腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。 返回 直…