栈在括号匹配中的应用
用栈实现括号匹配:
依次扫描所有字符,遇到左括号入栈,遇到右括号则弹出栈顶元素检查是否匹配。
匹配失败情况:
①左括号单身②右括号单身③左右括号不匹配
代码实现
cpp
#define MaxSize 10 //定义栈中元素的最大个数
typedef struct {
char data[MaxSize];//静态数组存放栈中元素
int top;//栈顶指针
}SqStack;
cpp
void InitStack(SqStack &S); //初始化栈
bool StackEmpty(SqStack S); //判断栈是否为空
bool Push(SqStack &S, char x); //新元素入栈
bool Pop(SqStack &S, char &x); //栈顶元素出栈,用x返回
cpp
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; //匹配失败
char topElem;
Pop(S, topElem);//栈顶元素出栈
if (str[i] == ')'&&topElem != '(')
return false;
if (str[i] = ']'&&topElem != '[')
return false;
if (str[i] = '}'&&topElem != '{')
return false;
}
}
return StackEmpty(S);
}
栈在表达式求值中的应用
表达式基本知识
表达式由三个部分组成:操作数、运算符、界限符。界限符是必不可少的,其反映了计算的先后顺序。
波兰表达式=前缀表达式,逆波兰表达式=后缀表达式。
一个中缀表达式可以对应多个后缀、前缀表达式。
中缀转后缀的手算方法:
① 确定中缀表达式中各个运算符的运算顺序
② 选择下一个运算符,按照「左操作数 右操作数 运算符」的方式组合成一个新的操作数
③ 如果还有运算符没被处理,就继续 ②
"左优先"原则:只要左边的运算符能先计算,就优先算左边的。
用栈实现后缀表达式的计算
①从左往右扫描下一个元素,直到处理完所有元素
②若扫描到操作数则压入栈,并回到①;否则执行③
③若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到①
注意:先出栈的是"右操作数"
中缀转前缀的手算方法
① 确定中缀表达式中各个运算符的运算顺序
② 选择下一个运算符,按照「运算符 左操作数 右操作数」的方式组合成一个新的操作数
③ 如果还有运算符没被处理,就继续 ②
"右优先"原则:只要右边的运算符能先计算,就优先算右边的。
用栈实现前缀表达式的计算
①从右往左扫描下一个元素,直到处理完所有元素
②若扫描到操作数则压入栈,并回到①;否则执行③
③若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到①
注意:先出栈的是"左操作数"
左优先和右优先原则:一个中缀表达式只对应一个后缀表达式(确保算法的"确定性")
中缀表达式转后缀表达式(机算)
初始化一个栈,用于保存暂时还不能确定运算顺序的运算符
从左到右处理各个元素,直到末尾。可能遇到三种情况:
① 遇到操作数。直接加入后缀表达式。
② 遇到界限符。遇到"("直接入栈;遇到")"则依次弹出栈内运算符并加入后缀表达式,直到
弹出"("为止。注意:"("不加入后缀表达式。
③ 遇到运算符。依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式,
若碰到"(" 或栈空则停止。之后再把当前运算符入栈。( * / 优先级高于 + - )
按上述方法处理完所有字符后,将栈中剩余运算符依次弹出,并加入后缀表达式。
中缀表达式的计算(用栈实现)
中缀转后缀+后缀表达式求值
初始化两个栈,操作数栈和运算符栈
若扫描到操作数,压入操作数栈
若扫描到运算符或界限符,则按照"中缀转后缀"相同的逻辑压入运算符栈(期间也会弹出运算符,每当弹出一个运算符时,就需要再弹出两个操作数栈的栈顶元素并执行相应运算,运算结果再压回操作数栈)
栈在递归中的应用
函数调用
函数调用的特点:最后被调用的函数最先执行结束(LIFO)
函数调用时,需要一个栈储存(函数调用栈):①调用返回地址 ②实参 ③局部变量
递归
它通常把一个大型的复杂问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的代码就可以描述岀解题过程所需要的多次重复计算,大大减少了程序的代码量。但在通常情况下,它的效率并不是太高。
递归调用时,函数调用栈可称为"递归工作栈",每进入一层递归,就将递归调用所需信息压入栈顶,每退出一层递归,就从栈顶弹出相应信息。直到程序执行到临界条件,然后便会把保存在栈中的值按照先进后出的顺序一个个返回,最终得出结果。
必须注意递归模型不能是循环定义的,其必须满足下面的两个条件
- 递归表达式(递归体)
- 边界条件(递归出口)
缺点:太多层递归可能会导致栈溢出,可能包含很多重复计算。
队列的应用
树的层次遍历与图的广度优先遍历(本章不做叙述)
在操作系统中的应用
多个进程争抢着使用有限的系统资源时,FCFS(First Come First Service,先来先服务)是一种常用策略。
如:CPU资源的分配