3.6 栈和队列的应用
3.6.1 括号匹配
-
问题:输入一串包含"(“”)“”[“”]“”{“”}"的字符串,判断这些括号是否能够正确匹配
-
思想:
- 初始时设置一个空栈,用于存储读入的左括号
- 若是左括号,则将其入栈,置于栈顶
- 若是右括号,则弹出栈顶元素,判断其和栈顶元素是否匹配,若不匹配或者栈空则结束算法,匹配失败
- 读取下一个字符,重复2~3步骤;若无下一个字符并且栈空,则匹配成功,若栈非空,则匹配失败
-
代码:
#define MaSize 100 // 定义栈中最大元素个数 typedef struct{ char data[MaxSize]; //静态数组中存放栈元素 int top; //栈顶指针 }SqStack; // 初始化栈 bool InitStack(SqStack &S); // 判断栈是否为空 bool StackEmpty(SqStack S); // 弹出栈顶元素 bool Pop(SqStack &S, char topElem); // 压栈 bool Push(SqStack &S, char e); bool bracketCheck(char str[], int length){ SqStack S; InitStack(S); for(int i=0;i<length;i++){ if(str[i]=='(' || str[i]=='[' || str[i]=='{'){ Push(S, str[i]); //扫描到左括号,入栈 } else{ //扫描到右括号 if(StackEmpty(S)) return false; //若此时栈空,则匹配失败 else{ char topElem; Pop(S, topElem); //栈顶元素出栈 if(topElem=='(' && str[i]!==')') return false; else if(topElem=='[' && str[i]!==']') return false; else if(topElem=='{' && str[i]!=='}') return false; } } } return StackEmpty(S); //检索完成后判断栈是否为空,栈空说明匹配成功 }
3.6.2 表达式求值
-
一个表达式:
((15/(7-(1+1)))*3)-(2+(1+1))
由操作数,运算符和界限符组成 -
一个灵感:可以不用界限符也能无歧义地表达运算顺序
-
逆波兰表达式(后缀表达式)
-
中缀转后缀
- 从左往右确定中缀表达式中各个运算符的运算顺序
- 选择下一个运算符,按照[‘左操作数’‘右操作数’‘运算符’]的方式组合成一个新的操作数
- 如果还有运算符没被处理,就继续②
-
15 7 1 1 - + / 3 * 2 1 1 + + -
-
后缀表达式手算方法:从左往右扫描,每遇到一个运算符,就让运算符前面最近的两个操作数执行对应运算,合体为一个新的操作数
-
中缀转后缀代码实现
/* * 1. 初始化一个栈,用于保存暂时还不能确定运算顺序的运算符 * 2. 从左往右处理各个元素,直到末尾,可能会遇到三种情况 * a. 遇到操作数,直接加入后缀表达式 * b. 遇到界限符,若遇到'('直接入栈;若遇到')'则一次弹出栈中的运算符并加入后缀表达式,直到弹出'('为止,注意,'('不加入后缀表达式 * c. 遇到运算符,一次弹出栈中优先级高于或等于自身的运算符,并加入后缀表达式,直到遇到'('或栈空为止,并再把当前运算符入栈 * 3. 按上述方法处理完所有字符后,将栈中剩余运算符依次弹出并加入后缀表达式 */
-
中缀表达式计算
/* * 1. 初始化两个栈,操作数栈和运算符栈 * 2. 若扫描到操作数,则压入操作数栈 * 3. 若扫描到运算符或界限符,则按照‘中缀转前缀’相同的逻辑将其压入运算符栈,期间每弹出一个运算符,也需要从操作数栈弹出两个操作数并执行响应运算,再将结果压入操作数栈 */
-
后缀表达式计算
/* * 1. 初始化一个栈用于存放操作数 * 2. 从左往右扫描下一个元素,直到处理完所有元素 * 3. 若扫描到一个操作数,则将其压入栈中,并返回2,否则进入4 * 4. 若扫描到一个运算符,则依次从栈中弹出两个操作数并执行运算,在将结果压入栈顶,返回1 */
-
-
波兰表达式(前缀表达式)
- 中缀转后缀
- 从右往左确定表达式中各个运算符的运算顺序
- 选择下一个运算符,按照[‘运算符’‘左操作数’‘右操作数’]的方式组合成一个新的操作数
- 如果还有运算符没被处理,就继续②
- / 15 - 7 + 1 1 3 + 2 + 1 1
- 中缀转后缀
-
-
中缀,后缀,前缀表达式
中缀表达式 后缀表达式 前缀表达式 a+b ab+ +ab a/b a/b /ab a+b-c ab+c- -+abc a+b-c*d ab+cd*- -+ab*cd
3.6.3 递归
- 递归:若在一个函数、过程或数据结构的定义中应用了它自身,则这个函数,过程,数据结构称为是递归定义的,简称递归
- 特点:
- 最后被调用的函数最先执行结束
- 代码简洁
- 思想:把一个大问题层层转化为一个与原问题相似的较小规模的小问题来求解,递归问只需少量代码求可以描述出解题过程所需的多次重复计算,大大减少了程序的代码量。通常情况下,它的效率并不是很高(因为递归调用过程中包含很多重复运算)
- 递归满足条件
- 递归表达式(递归体)
- 边界条件(递归出口)
- 精髓:能否将原始问题转化为属性相同但规模较小的子问题
- 递归转为非递归,通常需要借助栈来实现这种转换
注意:在递归调用的过程中,系统为每一层的返回点,局部变量,传入实参等开辟了递归工作栈来存储数据,递归调用过多容易造成栈溢出
3.6.4 层次遍历
3.6.5 计算机系统
- 解决主机与外部设备之间速度不匹配的问题
- 主机和打印机之间速度不匹配问题:主机传输数据速度大大快于打印机打印1速度,因此主机会将待打印数据传输到缓冲区当中,打印机则依次打印缓冲区中的数据,缓冲区就是一个队列;主机在缓冲区满后即可暂停传输数据,转去完成其它任务,提高了效率
- 解决由多用户引起的资源竞争问题
- CPU资源竞争:在一个带有多终端的计算机系统上,有多个用户需要在CPU上运行自己的程序。当这些用户终端向操作系统发送使用CPU请求时,操作系统通常会按照请求的先后次序它们排成一个队列,每次把CPU分配给队首的用户使用,该用户使用完后则令其出队,并将CPU分配给新的队首用户使用