栈的应用
括号匹配
最后出现的的左括号最先被匹配(LIFO)
将左括号依次压入栈中,越晚压入栈的左括号,越早被弹出
可用栈实现该特性
遇到左括号,压入栈中;遇到右括号,就把位于栈顶的左括号弹出,消耗(出栈)一个左括号,然后检查两个是否匹配(小对小,中对中,大对大)。
举例:
匹配情况 :
不匹配情况:
1.当前扫描到的右括号与栈顶左括号不匹配
2.扫描到右括号且栈空(右括号单身)
3.处理完所有括号后,栈非空(左括号单身)
流程图:
代码实现:
#include<stdio.h>
#define MAX 10
//顺序栈定义
typedef struct{
char data[MAX];
int top;
}SqStack;
bool bracket(char str[],int length){
SqStack S;
S.top=-1;
for(int i=0;i<length;i++){
if (str[i]=='{' || str[i]=='['|| str[i]=='('){
//左括号压入栈中
if (S.top==MAX-1)
return false;
S.top=S.top+1;
S.data[S.top]=str[i];
}
else {
//判断栈是否为空,右括号单身
if (S.top==-1)
return false;
//判断右括号是否和栈顶的左括号匹配
char topElem;
topElem=S.data[S.top];
S.top=S.top-1;
if (str[i]=='}'&& topElem!='{')
return false;
if(str[i]==']'&&topElem!='[')
return false;
if(str[i]==')'&&topElem!='(')
return false;
}
}//判断左括号单身
return (S.top==-1);
}
int main(){
char str[] = {'(','(',')',')'};
bool b = bracket(str, 4);
if(b == true){
printf("正确");
}else{
printf("不正确");
}
return 0;
}
表达式求值
算术表达式由三部分组成:操作数、运算符、界限符
后缀表达式(逆波兰表达式):运算符在两个操作数后面
前缀表达式(波兰表达式):运算符在两个操作数前 面
中缀表达式:运算符在两个操作符中间
中缀转后缀的手算方法:
- 确定中缀表达式中各个运算符的运算顺序
- 选择下一个运算符,按照「左操作数 右操作数 运算符」的方式组合成一个新的操作数
- 如果还有运算符没被处理,就继续2.
左优先原则:
只要左边的运算符能先计算,就优先算左边的
可保证运算顺序唯一
后缀表达式的手算方法:
从左往右扫描,每遇到一个运算符,就让运算符前面最近的两个操作数执行对应运算,合体为一个操作数。
- 特点:最后出现的操作数先被运算 LIFO 栈
用栈实现后缀表达式的计算:
- 从左往右扫描下一个元素,直到处理完所有元素
- 若扫描到操作数则压入栈,并回到①;否则执行③
- 若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到①
注意:
- 先出栈的是“右操作数”
- 此栈用于存放当前暂时还不能确定运算次序的操作数
中缀表达式转后缀表达式(机算):
初始化一个栈,用于保存 暂时还不能确定运算顺序的运算符。
从左到右处理各个元素,直到末尾。可能遇到三种情况:
- 遇到操作数。直接加入后缀表达式。
- 遇到界限符。遇到 “(” 直接入栈;遇到 “)” 则依次弹出栈内运算符并加入后缀表达式,直到弹出“(”为止。注意: “(” 不加入后缀表达式,弹飞它。
- 遇到运算符。依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式。
若碰到 “(” 或栈空则停止弹出。
之后再把当前运算符入栈。
按上述方法处理完所有字符后,将栈中剩余运算符依次弹出,并加入后缀表达式。
中缀转前缀的手算方法:
- 确定中缀表达式中各个运算符的运算顺序
- 选择下一个运算符,按照「运算符 左操作数 右操作数」的方式组合成一个新的操作数 如果还有运算符没被处理,就继续2.
左优先原则:只要右边的运算符能先计算,就优先算右 边的
可保证运算顺序唯一
用栈实现前缀表达式的计算:
- 从右往左扫描下一个元素,直到处理完所有元素
- 若扫描到操作数则压入栈≥并回到①;否则执行③
- 若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到①
注意:先出栈的是“左操作数”
中缀表达式的计算(用栈实现):
中缀转后缀+后缀表达式求值 两个算法的结合
- 初始化两个栈,操作数栈和运算符栈
- 若扫描到操作数,压入操作数栈
- 若扫描到运算符或界限符,则按照“中缀转后缀”相同的逻辑压入运算符栈(期间也会弹出运算符,每当弹出一个运算符时,就需要再弹出两个操作数栈的栈顶元素并执行相应运算,运算结果再压回操作数栈)
递归
函数调用的特点:最后被调用的函数最先执行结束(LIFO)
函数调用时,需要用一个栈存储:
- 调用返回地址
- 实参
- 局部变量
适合用“递归”算法解决:可以把原始问题转换为属性相同,但规模较小的问题
- 递归调用时,函数调用栈可称为“递归工作栈”
- 每进入一层递归,就将递归调用所需信息压入栈顶
- 每退出一层递归,就从栈顶弹出相应信息
缺点:效率低太多层递归可能会导致栈溢出;可能包含很多重复计算。
队列的应用
树的层次遍历
初始化:1入队
以下重复:队首元素出队,他的两个孩子入队
- 首先遍历根节点,即1号节点,遍历到1号节点时,把其左节点2和右节点3放到队尾
- 遍历完1号节点后1号出队
- 检查队头的2号节点,将2号节点的左右两个孩子45加到队列队尾
- 处理完2号节点后2号出队
- 检查队头的3号节点,将3号节点的左右两个孩子67加到队列队尾
- 处理完3号节点后3号出队
- 检查队头的4号节点,4号节点没有左右孩子,不需要在队尾插入任何元素
- 4号出队
- …
图的广度优先遍历
- 遍历1号节点
- 检查和1号节点相邻的节点有没有被遍历过,将23加入队列
- 1号出队
- 检查和1号节点相邻的节点有没有 没有被遍历过的节点,将4加入队列
- 2号出队
- 检查和3号节点相邻的节点有没有 没有被遍历过的节点,将56加入队列
- 3号出队
- 和4号相邻的节点都被处理过,4号出队
- 检查5号,将78加入队列
- 5号出队,6、7、8出队
队列在操作系统中的应用
多个进程争抢着使用有限的系统资源时,FCFS (First Come First Service,先来先服务)是一种常用策略。
CPU:
各个APP轮换使用
打印:
可缓解主机与打印机速度不匹配的问题