栈在括号匹配中的应用
// 数据结构#define MaxSize 10typedef struct {char data[MaxSize];int top; // 栈顶指针} SqStack;// 初始化栈void InitStack(SqStack &S)// 判断栈是否为空bool StackEmpty(SqStack S)// 入栈bool Push(SqStack &S, char x)// 出栈bool Pop(SqStack &S, char &x)
// 括号匹配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); // 检查完全部括号后,栈空说明匹配成功}// BracketCheck.cpp
栈在表达式求值的应用
算数表达式
算数表达式由三个部分组成:操作数、运算符、界限符(即括号,反映计算的先后顺序)
- 中缀表达式:运算符在两个操作数的中间,如
a + b,a + b - c,a + b - c * d - 后缀表达式:运算符在两个操作数的后面,如
a b +,a b + c -,a b + c d * - - 前缀表达式:运算符在两个操作数的前面,如
+ a b,- + a b c,- + a b * c d中缀转后缀
中缀转后缀的手算方法:
- 确定中缀表达式中各个运算符的运算顺序
- 选择下一个运算符,按照「左操作数 右操作数 运算符」的方式组合成一个新的操作数
- 如果还有运算符没被处理,就继续步骤2
注意:保证手算和计算机算结果相同,需要遵循“左优先”原则,即只要左边的运算符能先计算就优先计算左边的。(可以保证运算顺序唯一)
中缀转后缀的机算方法:
- 初始化一个栈,用于保存暂时还不能确定运算顺序的运算符。
- 从左到右处理各个元素,直到末尾。可能遇到三种情况:
- 遇到操作数。直接加入后缀表达式
- 遇到界限符。遇到
(直接入栈;遇到)则依次弹出栈内运算符并加入后缀表达式,直到弹出(为止。注意:)不加入后缀表达式。 - 遇到运算符。依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式,若碰到
(或栈空则停止。之后再把当前运算符入栈。
- 按上述方法处理完所有字符后,将栈中剩余运算符依次弹出,并加入后缀表达式。
后缀表达式结果的手算方法:从左往右扫描,每遇到一个运算符,就让运算符前面最近的两个操作数执行对应运算,合体为一个操作数。
用栈实现后缀表达式的计算:
- 从左往右扫描下一个元素,直到处理完所有元素
- 若扫描到操作数则压入栈,并回到步骤1;否则执行步骤3
若扫描到运算符,则弹出两个栈顶元素(先出栈的是“右操作数”),执行相应运算,运算结果压回栈顶,回到步骤1
中缀转前缀
前缀表达式较后缀表达式用的较少
中缀转前缀的手算方法:确定中缀表达式中各个运算符的运算顺序
- 选择下一个运算符,按照「运算符 左操作数 右操作数」的方式组合成一个新的操作数
- 如果还有运算符没被处理,就继续步骤2
注意:同样为保证手算和计算机算结果相同,需要遵循“右优先”原则,即只要右边的运算符能先计算,就优先算右边的。
用栈实现前缀表达式的计算:
- 从右往左扫描下一个元素,直到处理完所有元素
- 若扫描到操作数则压入栈,并回到步骤 1;否则执行步骤 3
若扫描到运算符,则弹出两个栈顶元素(先出栈的是“左操作数”),执行相应运算,运算结果压回栈顶,回到步骤 1
中缀表达式的计算
用栈实现中缀表达式的计算(中缀转后缀+后缀表达式求值):
初始化两个栈,操作数栈和运算符栈
- 若扫描到操作数,压入操作数栈
- 若扫描到运算符或界限符,则按照“中缀转后缀”相同的逻辑压入运算符栈(期间也会弹出运算符,每当弹出一个运算符时,就需要再弹出两个操作数栈的栈顶元素并执行相应运算,运算结果再压回操作数栈)
栈在递归的应用
函数调用的特点:最后被调用的函数最先执行结束(LIFO)
函数调用时,需要用一个栈存储:调用返回地址;实参;局部变量
适合用“递归”算法解决:可以把原始问题转换为属性相同,但规模较小的问题。必须注意递归模型不能是循环定义的,其必须满足下面的两个条件,即递归表达式(递归体)和边界条件(递归出口)。
缺点:太多层递归可能会导致栈溢出
例如,计算正整数的阶乘 :
%3D%5Cbegin%7Bcases%7D%0A%20%20n*%5Cmathrm%7Bfactorial%7D(n-1)%2C%20%26%20n%3E1%20%5C%5C%5C%5C%20%20%0A%20%201%2C%20%26%20n%3D1%20%5C%5C%5C%5C%0A%20%201%2C%20%26%20n%3D0%0A%5Cend%7Bcases%7D#card=math&code=%5Cmathrm%7Bfactorial%7D%28n%29%3D%5Cbegin%7Bcases%7D%0A%20%20n%2A%5Cmathrm%7Bfactorial%7D%28n-1%29%2C%20%26%20n%3E1%20%5C%5C%5C%5C%20%20%0A%20%201%2C%20%26%20n%3D1%20%5C%5C%5C%5C%0A%20%201%2C%20%26%20n%3D0%0A%5Cend%7Bcases%7D&id=xvEGD)
// 计算正整数 n!
#include <iostream>
using namespace std;
int factorial(int n) {
if (n == 0 || n == 1) {
return 1;
} else {
return n * factorial(n - 1);
}
}
int main() {
int x = factorial(3);
printf("%d", x);
}
求斐波那契数列:
%3D%5Cbegin%7Bcases%7D%0A%20%20%5Cmathrm%7BFib%7D(n-1)%2B%5Cmathrm%7BFib%7D(n-2)%2C%20%26%20n%3E1%20%5C%5C%5C%5C%20%20%0A%20%201%2C%20%26%20n%3D1%20%5C%5C%5C%5C%0A%20%200%2C%20%26%20n%3D0%0A%5Cend%7Bcases%7D#card=math&code=%5Cmathrm%7BFib%7D%28n%29%3D%5Cbegin%7Bcases%7D%0A%20%20%5Cmathrm%7BFib%7D%28n-1%29%2B%5Cmathrm%7BFib%7D%28n-2%29%2C%20%26%20n%3E1%20%5C%5C%5C%5C%20%20%0A%20%201%2C%20%26%20n%3D1%20%5C%5C%5C%5C%0A%20%200%2C%20%26%20n%3D0%0A%5Cend%7Bcases%7D&id=eXls7)
// 计算斐波那契数列
#include <iostream>
using namespace std;
int Fib(int n) {
if (n == 0) {
return 0;
} else if (n == 1) {
return 1;
} else {
return Fib(n - 1) + Fib(n - 2);
}
}
int main() {
for (int i = 0; i <= 10; i++) {
printf("%d,", Fib(i));
}
}

显然,在递归调用的过程中,Fib(3) 被计算了2次,Fib(2) 被计算了3次。Fib(1) 被调用了5次,Fib(0)被调用了3次。所以,递归的效率低下,但优点是代码简单,容易理解。
可以将递归算法转换为非递归算法,通常需要借助找来实现这种转换。
队列在层次遍历中的应用
层次遍历二叉树的过程:
- 根节点入队
- 若队空(所有结点都已处理完毕),则结束遍历;否则重复步骤3操作。
- 队列中第一个结点出队,并访问之。若其有左孩子,则将左孩子入队;若其有右孩子,则将右孩子入队,返回步骤2。

队列在图的广度优先遍历的应用
队列在计算机系统中的应用
多个进程争抢着使用有限的系统资源时,FCFS ( First Come First Service)先来先服务是一种常用策略。
