栈在括号匹配中的应用
假设表达式中允许包含两种括号:圆括号和方括号,其嵌套的顺序任意,形如"()“等为正确的格式,形如”[(])“,”([())"等为错误的格式
考虑下列括号序列:
[ ( [ ] [ ] ) ]
1 2 3 4 5 6 7 8
分析如下:
- 计算机接收第一个括号"[“后,期待与之匹配的第9个括号”]"出现
- 获得了第2个括号"(“,此时第一个括号”[“暂时放在一边,而急迫期待与之匹配的第7个括号”)"出现。
- 获得了第3个括号"[“,此时第2个括号”(“暂时放在一边,而急迫期待与之匹配的第4个括号”]"出现。第3个括号的期待得到满足,消解之后,第2个括号的期待匹配又称为当前最急迫的人物
- 以此推类
算法的构思如下:
- 初始设置一个空栈,顺序读入括号
- 若是右括号,则或者使制语栈顶的最急迫的期待压入栈中,或者是不合法的情况(若括号不匹配,直接返回false)
- 若是左括号,则作为一个新的更紧急的期待压入栈中,自然使原有的在栈中的所有未消解的急迫性降一级。算法结束的时候,若栈为空,则序列匹配,若栈不为空,则序列不匹配
C++代码如下:
//括号匹配
bool ParenthesisMatch (char str [] , SqStack S){
int x;
for (int i = 0; i < strlen(str); i++) {
if (str[i]=='('||str[i]=='['){
Push(S , str[i]);
}
if (str[i]==')'||str[i]==']'){
Pop(S , x);
if (str[i] == char(x))continue;
else return false;
}
}
return StackEmpty(S);
}
栈在表达式求值中的应用
栈应用的一个典型范例。我们先将中缀表达式转换成后缀表达式(这样到后面计算的时候不需要考虑括号),之后再计算后缀表达式。
通过后缀表达式计算表达式值的过程为:顺序扫描表达式的每一项,然后根据它的类型做如下相应操作:若该项是操作数,则将其压入栈中;若该项是操作符,则连续从栈中退出两个操作数Y和X,形成运算指令XY,并将计算结果重新压入栈中。当表达式的所有项都扫描并处理完后,栈顶存放的就是最后的计算结果
JAVA代码如下:
import java.util.*;
import java.io.*;
class Main{
public static void main (String [] args) throws IOException {
BufferedReader br = new BufferedReader (new InputStreamReader (System.in));
//定义两个栈,一个用来存数字,一个用来存符号
Stack <Character> ops = new Stack <> ();
Stack <Integer> nums = new Stack <> ();
//定义一个字符数组,用来输入多项式
char [] chs = br.readLine().toCharArray();
//定义一个哈希表,用来存储不同符号的优先级
HashMap <Character , Integer> map = new HashMap <> ;
map.put('+',1);
map.put('-',1);
map.put('*',2);
map.put('/',2);
map.put('(',-1);
//定义一个字符串,用于存储数字
StringBuilder sb = new StringBuilder();
for(int i = 0 ; i < chs.length ; i++){//对多项式进行遍历
char c = chs[i];
if(Character.isDigit(c)){//如果遍历的字符是数字,则将其转化为int型数据,压栈压入nums栈中
sb.append(c);
int j = i+1;
while (j<chs.length&&Character.isDigit(chs[j])){
sb.append(chs[j]);
j++;
}
nums.push(Integer.parseInt(sb.toString()));
sb.delete(0,sb.length());
i = j-1;
}
else if (c=='('){//如果遍历的字符是左括号,则压入ops栈中
ops.push(c);
}
else if (c==')'){//如果遍历的字符是右括号,则进行算数计算,最后把左括号弹栈
while (ops.peek()!='('){
eval(ops,nums);
}
ops.pop();
}
else{//在符号栈为空之前以及栈顶符号的优先级大于正遍历到的字符时,进行算数运算,结束之后将正在遍历的符号压栈
while (!ops.isEmpty()&&(map.get(ops.peek())>=map.get(c))) eval(ops,nums);
ops.push(c);
}
}
while (!ops.empty()) eval(ops,nums); //在符号栈为空之前,进行算数运算
System.out.print(nums.peek());
}
public static void eval (Stack <Character> ops , Stack <Integer> nums ){
int num1 = nums.pop();
int num2 = nums.pop();
char op = ops.pop();
if (op=='+') nums.push(num1+num2);
else if (op=='-') nums.push(num2-num1);
else if (op=='*') nums.push(num2*num1);
else if (op=='/') nums.push(num2/num1);
}
}
C++代码如下:
//多项式求值
void eval(SqStack op , SqStack num)
{
int a ;
int b ;
int c ;
Pop(num , b);
Pop(num , a);
Pop(op , c);
if (c == int('+'))Push(num , a+b);
else if (c == int('-'))Push(num , a-b);
else if (c == int ('/'))Push(num , a/b);
else if (c == int ('*'))Push(num , a*b);
}
void MultinomialCalculate(SqStack op , SqStack num , int & x , string str){
unordered_map<char , int > pr{{'+',1},{'-',1},{'*',2},{'/',2}};
for (int i = 0; i < str.size(); i++) {
auto c = str[i];
if(isdigit(c)){
int x = 0 ;
int j = i ;
while(j<str.size()&& isdigit(str[i])){
x = x*10+int(str[j++]);
}
i = j-1;
}
else if (c == '('){
Push(op , int('('));
}
else if (c == ')'){
int Left_op;
GetTop(op,Left_op);
while(Left_op!=int('(')){
eval(op , num);
}
}
else {
while(!StackEmpty(op)&& op.data[op.top] != int ('(')&&pr[char(op.data[op.top])]>pr[c])eval(op , num);
Push(op , c);
}
}
while (!StackEmpty(op))eval(op , num);
cout<<num.data[num.top]<<endl;
}
栈在递归中的应用
递归是一种重要的程序设计方法。简单的说,若在一个函数、过程或数据结构的定义中应用了它自身,则这个函数、过程或数据结构称为是递归定义的,也就是递归。
它通常把一个大型的复杂问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的代码就可以描述出解题过程所需要的多次重复计算,大大减少了程序的代码量。但是在通常情况下,它的效率并不是太高。
我们可以将递归算法转换为非递归的算法,通常需要借助栈来实现这种转换。
注:消除递归有两种常用的方法:1、使用栈来模拟递归 2、使用迭代思想
队列在层次遍历中的应用(队列在迷宫问题中的应用)
JAVA实现:
import java.util.*;
import java.io.*;
class Main {
public static int N = 110;
public static int [][] map = new int [N][N]; //map表用于存储地图(题给01矩阵)
public static int [][] pathNum = new int [N][N]; //pathNum表用于存储该点到原点的距离
public static void main (String [] args) throws IOException {
BufferedReader br = new BufferedReader (new InputStreamReader (System.in));
String [] mn = br.readLine().split(" ");
int n = Integer.parseInt(mn[0]);
int m = Integer.parseInt(mn[1]);
for (int i = 0 ; i < n ; i++){
String [] arr = br.readLine().split(" ");
for (int j = 0 ; j < m ; j++){
map[i][j] = Integer.parseInt(arr[j]);
}
}
System.out.print(bfs(n,m));
}
public static int bfs (int n , int m){
Queue <int[]> queue = new LinkedList <> () ;
pathNum[0][0] = 0;//起始点到自己的距离为0
int []dx = {0,1,0,-1}; //四个方向移动
int []dy = {-1,0,1,0};
queue.offer(new int [] {0,0});
while(!queue.isEmpty()){
int a [] = queue.poll(); //用a数组存储点的位置(x,y)
for(int i = 0 ; i < 4 ; i++){
int x = a[0]+dx[i];
int y = a[1]+dy[i];
if(x>=0&&x<n&&y>=0&&y<n&&map[x][y]==0&&pathNum[x][y]==0){
pathNum[x][y] = pathNum[a[0]][a[1]]+1; //路径数+1
queue.offer(new int [] {x,y});
}
}
}
return pathNum[n-1][m-1];
}
}
队列在计算机系统中的应用(和操作系统对接)
队列在操作系统的应用主要有两个方面:1、解决主机与外部设备之间速度不匹配的问题 2、解决由多用户引起的资源竞争问题
对于第一个方面,以打印机和主机之间的速度不匹配问题做说明。主机输出数据给打印机打印,输出数据的速度逼打印数据的速度要快很多,由于速度不匹配,若直接把 输出的数据送给打印机打印显然是不行的。解决的方法是设置一个答应数据缓冲区,主机要把打印输出的数据依次写入这个缓冲区,写满后就暂停输出,转去做其它事情。答应及就从缓冲区中按照先进先出的原则依次取出数据并打印,打印完后再向主机发出请求。主机接到请求后再向缓冲区写入打印数据。这样做既保证了打印数据的正确,又使主机提高了效率。由此可见,打印数据缓冲区中所存储的数据就是一个队列(操作系统中的Spooling技术)
对于第二个方面,CPU资源的竞争就是一个典型的例子。在一个带有多终端的计算机系统上,有多个用户需要CPU各自运行自己的程序,他们分别通过各自的终端向操作系统提出占用CPU的请求。操作系统通常按照每个请求在时间上的先后顺序,把它们排成一个队列,每次把CPU分配给队首请求的用户使用。当相应的程序运行结束或用完规定的时间间隔后,令其出队或加入到队尾,再把CPU分配给新的队首请求的用户使用。这样既能满足每个用户的请求,又使CPU能够正常运行。
注:
- 迷宫求解有三种方法:1、 栈 2、 队列 3、回溯
- 页面替换算法有四种方法:1、最佳置换算法(OPT算法,同时也是无法实现的理想算法) 2、先进先出算法(FIFO算法,使用了队列) 3、最近最久未使用算法(LRU算法) 时钟(CLOCK算法,性能最接近OPT算法的算法) 其中,时钟算法还有一个改进版本
- 非递归算法的效率通常要比递归算法高一些