本文介绍队列和栈的实际应用:括号匹配、表达式求值
一、括号匹配问题
【问题描述】:给定一个仅含有括号的字符串,如何去判断该括号序列是否合法呢?
例如:()()合法,(())合法,【()】合法
但((()不合法,【)【)不合法
用栈来解决。
下面开始模拟算法,走两遍
比如第一种情况:【()】【】
参考上面的算法思想,执行过程:
1、先看第一个括号,因为是左括号,所以直接把第一个括号【,压入栈中。
2、再看第二个,也是左括号(,所以直接扔进栈就行
3、第三个括号是右括号),所以我们要弹出一个栈顶元素 ( 进行匹配,发现两者正好匹配,然后继续。现在栈中就只有一个元素【了。
4、第四个括号是】,同上,因为是右括号,所以弹出栈顶元素,然后发现是匹配的。继续,现在栈中没有元素了。
5、第五个是左括号,入栈
6、第六个是右括号,同三,匹配。现在栈中又没有元素了
7、字符串读取完毕,且此时栈中为空,所以字符串是满足括号匹配的!
情况2、字符串:【()
执行过程:
1、第一个括号为左括号,直接入栈
2、第二个括号为左括号,直接入栈
3、第三个括号为),弹出栈顶元素(,并检查是否匹配,发现两者匹配,因此继续。现在栈中元素为【
4、字符串读取完毕,但栈不空,所以该括号字符串不满足括号匹配!
情况3、字符串:【(】【】
执行过程:
1、第一个括号为左括号,直接入栈
2、第二个括号为左括号,直接入栈
3、第三个括号为右括号,所以弹出栈顶元素(,检查发现(和】是不匹配的,因此算法直接return false。该字符串不匹配。
二、表达式求值
先介绍三种表达式:
那么复杂的表达式,如何完成中缀转前缀呢?
中缀转前缀
核心思想,比如(A+B)*C
我们先把括号内部优先级高的,A+B转成前缀:+AB
然后把(+AB) *C种的+AB看作一个数
因此,最后的转换结果是:
*+ABC
中缀转后缀
思路和转前缀一样
用栈实现的算法模拟:
我们以中缀转后缀为例子
执行过程:
1、检测到是左括号(,入栈
2、同上,左括号入栈
3、检测到是数字A,于是将其抄写下来,此时后缀表达式为:A
4、运算符+,根据规则c,运算符优先级是高于左括号的,直接入栈
5、然后检测到数字B,于是抄写下来,此时后缀表达式为:AB
6、遇到右括号了,注意规则b,我们将栈中的运算符全部弹出加入到表达式中,然后直到遇到左括号,将其删除。此时后缀表达式为:AB+。栈中此时为:(
7、遇到*,乘号优先级比括号高,直接压到栈里面。
8、遇到数字C,将其抄写下,此时后缀表达式为:AB+C
9、遇到右括号,将乘号弹出,此时后缀表达式为:AB+C*。并且根据规则b,还要将栈中的左括号删除。那么此时栈中就空了
10、遇到-,因为栈空,直接入栈,
11、遇到左括号,加入栈中
12、遇到数字E,抄写下来,此时后缀表达式为:AB+C*E
13、遇到减号-,因为栈顶是(,所以直接入栈,此时栈内为:- ( -
三个元素
14、遇到数字F,抄写,此时后缀表达式为:AB+C * EF
15、遇到右括号,将减号弹出,加入到表达式中,并且把左括号删除
此时后缀表达式为 AB+C * EF-
16、根据规则d,遍历完成,将栈中所有元素弹出,加入到表达式里。于是弹出减号,此时后缀表达式为 AB+C * EF–
得到最后的答案。
这个算法手动模拟起来稍微麻烦一点,大家多试试,弄熟就好了
后缀表达式求值
那么我们幸幸苦苦学会了中缀表达式转后缀表达式,到底是为了什么呢?相信很多读者看到这里都会有这样的疑问。哈哈,其实就是因为用后缀表达式来求值是一件非常简单的事情!而中缀表达式交给计算机去直接算,那可就麻烦了。
因此,我们回了中缀转后缀之后,就可以将复杂的中缀表达式先转成后缀表达式,然后再用后缀表达式的求值算法来计算出最后的值。
那,说了这么多,后缀表达式求值到底有多简单呢?
算法思想:
利用一个栈,每次把操作数扔进栈,遇到一个运算符就把栈顶两个元素弹出做一个运算,然后再把运算结果压到栈中,循环以上过程,最后在栈里会得到一个数字,就是结果。
说的有点抽象,来个例子:
比如我们以上面那个后缀表达式为例子:
AB+C*EF–
比如我们取A = 3;B = 2;C = 2;E = 6;F = 4
那么根据其对应的中缀表达式【(A + B) * C】 - 【E - F】
我们可以算出,结果为8
下面我们用后缀表达式算法检验一下:
1、遇到数,无脑扔进栈。所以AB直接扔进,现在栈中元素:AB
2、遇到+,于是将栈顶两个元素AB弹出,做运算A+B = 3 + 2 = 5,将结果5压入栈
3、遇到C,直接入栈
4、遇到*,于是将C、5弹出,做运算C*5 = 5 * 2 = 10;将10压入栈
5、遇到E、F,直接无脑入栈,那么现在栈中:10, E, F三个元素
6、遇到-,将E、F弹出,做运算E - F = 6 - 4 = 2;将2压入栈
7、遇到-,将10、2弹出,做运算10 - 2 = 8。
8、字符串遍历完毕,栈中仅有的是结果,所以结果为8,经检验,算法正确!
所以今后做表达式求值算法的思路就很明确了,先将中缀表达式转后缀,然后再用后缀表达式求值算法求出结果即可。人比较擅长中缀表达式求值,但计算机更喜欢后缀表达式运算
完
【js代码实现】:
只针对+ - 的情况
//中缀转后缀,然后后缀求值
var calculateBack = function(s){
var stack = [];
var arr = s.split("");
for(let i = 0;i < arr.length;i++){
if(arr[i] == '+' || arr[i] == '-') continue;
arr[i] = Number.parseInt(arr[i]);
}
for(let i = 0;i < arr.length;i++){
if(arr[i] == '+' || arr[i] == '-'){
let x = stack[stack.length - 1];
stack.pop();
let y = stack[stack.length - 1];
stack.pop();
if(arr[i] == '+'){
let val = x + y;
stack.push(val);
}else{
let val = y-x; //注意减法顺序不要反了
stack.push(val);
}
}else{
stack.push(arr[i]);
}
}
return stack[0];
}
//中缀转后缀
var midToBack = function(s){
var res = [];
var temp = s.trim().split("");
var arr = [];
for(let i = 0;i < temp.length;i++){
if(temp[i] != ' '){
arr.push(temp[i]);
}
}
//下面为核心,上面都是预处理字符串
var stack = [];
for(let i = 0;i < arr.length;i++){
if(arr[i] == '('){
stack.push(arr[i]);
}else if(arr[i] == ')'){
while(stack[stack.length - 1] != '('){
res.push(stack[stack.length - 1]);
stack.pop()
}
stack.pop();
}else if(arr[i] == '+' || arr[i] == '-'){
if(stack.length != 0 && stack[stack.length - 1] != '('){
while(stack.length != 0 && stack[stack.length - 1] != '('){
res.push(stack[stack.length - 1]);
stack.pop()
}
}
stack.push(arr[i]);
}else{
res.push(arr[i]);
}
}
//栈中剩余元素直接加过去
while(stack.length != 0){
res.push(stack[stack.length - 1]);
stack.pop()
}
return res.join("");
}