以下笔记为jjyaoao本人制作,欢迎借鉴学习和提出相关建议,转载需要标明出处www.jjyaoao.space

4.栈

ADT接口

image-20220227162133925

操作实例

image-20220227162239492

实现

image-20220227162325009

实际应用

image-20220227162606296

进制转换

手动实现

image-20220227163152499

难点

image-20220227162848336

思路

image-20220227163220014

算法实现

image-20220227162830691

convert
1
2
3
4
5
6
7
void convert ( Stack<char>& S, __int64 n, int base ) { //整数n的1<base<=16进制打印(迭代版)
0002 char digit[] = "0123456789ABCDEF"; //数位符号,如有必要可相应扩充
0003 while ( n > 0 ) { //由低到高,逐一计算出新进制下的各数位
0004 S.push ( digit[ n % base ] ); //余数(当前位)入栈
0005 n /= base; //n更新为其对base的除商
0006 }
0007 } //新进制下由高到低的各数位,自顶而下保存于栈S中

括号匹配

问题描述

判断括号是匹配还是失配

image-20220227163441039

尝试

image-20220227163742641

构思

image-20220227163910264

实现

image-20220227164042171

1
2
3
4
5
6
7
8
9
10
11
12
bool paren ( const char exp[], int lo, int hi ) { //表达式括号匹配检查,可兼顾三种括号
0002 Stack<char> S; //使用栈记录已发现但尚未匹配的左括号
0003 for ( int i = lo; i <= hi; i++ ) /* 逐一检查当前字符 */
0004 switch ( exp[i] ) { //左括号直接进栈;右括号若与栈顶失配,则表达式必不匹配
0005 case '(': case '[': case '{': S.push ( exp[i] ); break;
0006 case ')': if ( ( S.empty() ) || ( '(' != S.pop() ) ) return false; break;
0007 case ']': if ( ( S.empty() ) || ( '[' != S.pop() ) ) return false; break;
0008 case '}': if ( ( S.empty() ) || ( '{' != S.pop() ) ) return false; break;
0009 default: break; //非括号字符一律忽略
0010 }
0011 return S.empty(); //最终栈空,当且仅当匹配
0012 }

反思

如果我们只考虑一个括号,又为什么不只用一个计数器来记录括号,而使用更加复杂的栈结构呢?

image-20220227164308858

原因在于……

采用栈,可以更便捷的推广到多种括号并存的情况

image-20220227164430548

教材与习题解析有这个的具体实现….

栈混洗

分别用尖括号和方括号来对应栈的顶部和底部

image-20220227164808305

计数

对于长度为n的序列,栈混洗总数可能是多少呢?

A -> S S -> B他们所需的栈混洗次数其实是相互独立的

SP(1) = 1 (平凡情况)

image-20220227165335882

甄别

image-20220227165546609

image-20220227165656037

image-20220227165923436

+括号匹配

image-20220227170915638

中缀表达式求值

构思

image-20220227171416090

难点

image-20220227171517176

栈+线性扫描

image-20220227171716297

实现

image-20220227172114493

readNumber
1
2
3
4
5
6
7
8
9
10
void readNumber ( char*& p, Stack<double>& stk ) { //将起始于p的子串解析为数值,并存入操作数栈
0002 stk.push ( ( double ) ( *p - '0' ) ); //当前数位对应的数值进栈
0003 while ( isdigit ( * ( ++p ) ) ) //若有后续数字(多位整数),则
0004 stk.push ( stk.pop() * 10 + ( *p - '0' ) ); //追加之(可能上溢)
0005 if ( '.' == *p ) { //若还有小数部分
0006 double fraction = 1; //则
0007 while ( isdigit ( * ( ++p ) ) ) //逐位
0008 stk.push ( stk.pop() + ( *p - '0' ) * ( fraction /= 10 ) ); //加入(可能下溢)
0009 }
0010 }
orderBetween
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Operator optr2rank ( char op ) { //由运算符转译出编号
0002 switch ( op ) {
0003 case '+' : return ADD; //加
0004 case '-' : return SUB; //减
0005 case '*' : return MUL; //乘
0006 case '/' : return DIV; //除
0007 case '^' : return POW; //乘方
0008 case '!' : return FAC; //阶乘
0009 case '(' : return L_P; //左括号
0010 case ')' : return R_P; //右括号
0011 case '\0': return EOE; //起始符与终止符
0012 default : exit ( -1 ); //未知运算符
0013 }
0014 }
0015
0016 char priority ( char op1, char op2 ) //比较两个运算符之间的优先级
0017 { return pri[optr2rank ( op1 ) ][optr2rank ( op2 ) ]; }
优先级表

image-20220227172224624

理解

image-20220227172254728

switch内具体细节

image-20220227172203606

理解:

相等情况时,观察优先级表,不难得出,相等的情况只有两个括号相遇,或者\0遇上\0也就是,需要’’(‘’ And ‘’)’’共同退出历史舞台,将注意力转移到下一个元素的时候,或者结束while循环的时候

而优先级表出现 > 的时候,即可以执行运算的时候,也就是即便栈顶元素和当前运算符相同,或者栈顶运算符大于当前运算符的时候,可以开始栈顶运算符的出栈,并且执行相应的运算

image-20220227172714925

思考

为什么不直接opnd.push(calcu( opnd.pop(), op , opnd.pop() ) )?

理解:

紧凑的写法对于讲究先后顺序的运算符会出错,比如对于除法运算,由于栈先进后出的性质,就成了除数/被除数,颠倒了

实例

image-20220227183905581

image-20220227183921843

image-20220227183931264

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

  • 摒弃掉约定俗成的优先级
  • 摒弃掉强制优先级的括号

算法框架

image-20220227185106809

实例

image-20220227184857349

中缀转后缀

image-20220227184947871

转换算法

image-20220227185017464

队列

ADT接口

image-20220227185339176

操作实例

image-20220227185402493

基于列表模板类

image-20220227185602290