综合应用
使用栈完成一个计算表达式的结果
输入:2 * 3 - 4 / 5 * 0.2 => 0.08
思路
- 使用index -> 2 (第一个为止),数字放入数栈,符号放入符号栈,符号栈为空,直接放入。
-
遇到第二个运算符,与符号栈中的符号进行运算符优先级比较。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gvhtZGbo-1601271684334)(C:\Users\how浩\AppData\Roaming\Typora\typora-user-images\image-20200925111655503.png)]
-
第二个优先级小于等于第一个运算符,则计算绿色框中表达式的结果;
- 运算结果存入栈;
- 将第二个运算符存入栈;
-
第二个优先级大于第一个运算符,直接进行数值运算,结果保存到数栈中;
-
为什么使用栈这个数据结构?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IXSde0V7-1601271684338)(C:\Users\how浩\AppData\Roaming\Typora\typora-user-images\image-20200925111845440.png)]
整个运算下来,“-”最先入“容器”,因为优先级不够的问题最后才出“容器”参与运算,刚好符号栈这个数据结构。
代码实现
function calculator(expression) {
expression = expression.replace(/ +/g, "");
let expressionArr = expression.match(/(\d+|[*\-+/%]+)/g);
console.log(expressionArr);
let numStack = new StackArray();
let signStack = new StackArray();
let signsValue = {"-": 1, "+": 1, "*": 2, "/": 2, "%": 1};
let sign = "";
for (let i = 0; i < expressionArr.length; i++) {
// 遍历到新的符号 且 符号栈中已经有符号
if (!signStack.isEmpty() && signsValue[expressionArr[i]]) {
// 窥探一下栈顶的符号
sign = signStack.peek();
// 如果当前符号优先级小于等于栈顶符号优先级,取出两个数字和栈顶符号进行运算
if (signsValue[expressionArr[i]] <= signsValue[sign]) {
let right = numStack.pop();
let left = numStack.pop();
let result = eval(left + signStack.pop() + right);
// 计算结果和当前符号放入栈,准备下一轮计算
numStack.push(result);
signStack.push(expressionArr[i]);
} else {
// 当前符号优先级大于栈顶符号优先级,只取出数栈顶的一个元素和运算符下一个数字进行运算
let curSign = expressionArr[i];
let right = expressionArr[++i];
let left = numStack.pop();
let result = eval(left + curSign + right);
numStack.push(result);
}
// 检测是否到最后一位字符
if (i === expressionArr.length - 1) {
let right = numStack.pop();
let left = numStack.pop();
let sign = signStack.pop();
result = eval(left + sign + right);
return result;
}
// 计算完没必要再放入栈中了
continue;
}
// 第一次两个栈都为空
if (!isNaN(expressionArr[i])) { // number
numStack.push(expressionArr[i]);
} else if (signsValue[expressionArr[i]]) { // sign
signStack.push(expressionArr[i]);
}
}
// 运算出最后的结果
let right = numStack.pop();
let left = numStack.pop();
let lastSign = signStack.pop();
return eval(left + lastSign + right);
}
逆波兰计算器
后缀表达式的计算器求值
如: (3+4) * 5 - 6 对应的后缀表达式就是 3 4 + 5 * 6 - ,针对后缀表达式的求值步骤:
- 从左至右扫描,将3和4压入栈;
- 遇到"+"运算符,弹出3和4,进行运算,结果压入栈;
- 遇到5,压入栈;
- 遇到"*"弹出7和5,将结果压入栈;
- 遇到6,压入栈;
- 遇到“-”,弹出35和6,进行运算,结果压入栈;
- 结束
实现后缀表达式计算器
输入一个后缀表达式 3 4 + 5 * 6 - => 29
function calculatePostfix(expression) {
let expressionArr = expression.match(/([\d]+|[\/*+\-])/g);
console.log(expressionArr);
let numStack = new StackArray();
for (let i = 0; i < expressionArr.length; i++) {
// number
if (!isNaN(expressionArr[i])) {
numStack.push(expressionArr[i]);
} else {
// sign
// 遇到符号就取出两个数字进行运算
let right = numStack.pop();
let left = numStack.pop();
let sign = expressionArr[i];
numStack.push(eval(left + sign + right));
}
}
return numStack.peek();
}
从代码实现中可以发现,后缀表达式相对中缀表达式计算非常方便,很符合计算机的运算方式,遇到符号就将数字取出栈运算,将结果放入栈。
中缀表达式转后缀表达式⭐️
🌰
1 + ( ( 2 + 3 ) * 4 ) - 5 => 1 2 3 + 4 * + 5 -
考虑以下几种情况:
top cur
① 1 - 2 + 3 - 4
② 1 * 2 + 3
③ 1 - 2 * 3
------------------------
④ 1 - (2 * (3 + 4)) * 5
用一个栈s1保存符号,用第二个栈s2保存后缀表达式。
扫描到数字直接进入s2。
top cur 代表符号优先级
① top 与 cur 的优先级相同, 转换:1 - 2 + 3 - 4=> 1 2 - 3 + 4 -
- "-"先入栈s1
- 扫描到"+",优先级相同,"-“先出栈,进入s2,”+"进入栈s1
- 扫描到"-",优先级相同,"+“先出栈,进入s2,”-"进入栈s1
- 扫描到最后一个数字,s1中的"-"出栈,进入s2
②top > cur,转换:1 * 2 + 3 => 1 2 * 3 +
- "*"先入栈s1
- 扫描到"+","*“弹出s1,入s2,”+"入s1
- 扫描到最后一个数字,s1中的"+"出栈,进入s2
③ top < cur,转换:1 - 2 * 3 => 1 2 3 * -
- "-"先入栈s1
- 扫描到"*","*“的下一个数字入栈s2,”*"入栈s2
- "-"入栈s2
()
关键思路分析:
- ( 2 + 3 )括号中一定会有一个运算符的优先级被提到最高。
- 如( 2 + 3 ) 先让
(
入栈,依次是+
,然后是)
就从栈中取出符号,并且取出(
。 - 如( ( 2 + 3 ) * 4 )
((+
入栈,遇到)
,取出+(
,此时+
就是被提高优先级的符号。*
入栈,遇到)
,取出*(
,此时*
就是被提高优先级的符号。
- 如( 2 + 3 ) 先让
如图:
()
中的运算符优先级提升,同时也运用了栈先进后出的概念。
④ 1 - (2 * (3 + 4)) * 5 转换:1 2 3 4 + * 5 * -
- "-"入栈s1
- "("入栈s1
- "*"入栈s1
- "("入栈s1
- "+"入栈s1
- 扫描到")",取出"+(","+"入栈s2,此时"+"运算优先级提高
- 扫描到")",取出"*(","*",入栈s2,"*"运算优先级提高,此时栈s1中仅剩下"-"
- 扫描到"*",根据③,"*“的下一个数字入栈s2,”*“入栈s2,”-"入栈s2
小结
-
数字:直接入栈
-
符号:
- 栈为空 or 栈顶为"(" or cur为"(",符号直接入栈
- cur为")",取出栈s1中的常规运算符,进入栈s2,弹出"(",不做任何操作
- cur为常规运算符,top为常规运算符,进行比较
- top >= cur,top出栈s1,进入s2,cur进入s1
- top < cur,cur下一个数字入栈s2,cur入栈s2,top出栈s1,入栈s2
- 根据②③,扫描到最后一个数字栈s1,可能为空也可能有符号,有符号就直接入栈s2
-
细节:最后s2符号出栈是逆序排列,因此需要颠倒过来。
代码实现
function calculatePostfix(expression) {
expression = expression.replace(/ +/g, "");
expressionArr = expression.match(/([\d+]|[()\-\/+*%])/g);
let signStack = new StackArray();
let numStack = new StackArray();
let signsValue = {"-": 1, "+": 1, "*": 2, "/": 2, "%": 1};
let result = [];
for (let i = 0; i < expressionArr.length; i++) {
// number
if (!isNaN(expressionArr[i])) {
numStack.push(expressionArr[i]);
} else {//sign
let cur = expressionArr[i];
let top = signStack.peek();
if (signStack.isEmpty() || top === '(' || cur === '(') {
signStack.push(cur);
} else if (cur === ')') {
numStack.push(signStack.pop());
// 弹出'('
signStack.pop();
} else { // 常规运算符号
let curValue = signsValue[cur];
let topValue = signsValue[top];
// 将top弹出s1
top = signStack.pop();
if (topValue >= curValue) {
numStack.push(top);
signStack.push(cur)
} else {
numStack.push(expressionArr[++i]);
numStack.push(cur);
numStack.push(top);
}
}
}
}
if (!signStack.isEmpty()) {
numStack.push(signStack.pop());
}
let j = 0;
while(!numStack.isEmpty()) {
result[j++] = numStack.pop();
}
return result.reverse().join('');
}