前缀表达式(波兰表达式)
前缀表达式又称波兰式,前缀表达式的运算符位于操作数之前。例如 (3+4)*5-6对应的前缀表达式就是- * + 3 4 5 6
思路
从右至左扫描表达式,遇到数字就压入栈中,遇到运算符时,弹出栈顶的两个数,用运算符对他们做相应的运算,,并将结果入栈,重复,直到表达式最左端,最后运算的得出的值就是表达式的结果。
如上例子:从右往左扫描,将6,5,4,3压入栈中
再次扫描下一个遇到+,就pop两数3和4进行加法运算,得到结果后又入栈
注:num1是栈顶元素,num2是次顶元素,
num1 符号 num2

直到最后栈中就剩下一个值,就是结果
中缀表达式
操作符号在中间,例如普通的算式3+4-8*2/4。中缀表达式对于人来说很好操作,对于计算机来说不太好,因为运算次序问题。一般计算都是转换成后缀表达式
思路
使用栈完成表达式的计算思路
- 定义两个栈,一个用来放数字,numStack,一个用来放操作符,operStack
- 定义一个index指针,字符串的索引
- 遍历字符串,发现index指向一个数字,就直接入numStack
- index指向一个符号,就分以下情况
- 如果发现符号栈为空,就直接入栈
- 如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或等于栈中的操作符号,就需要从数栈中pop出两个数(出栈的第二个数是运算式子的第一个数字),从符号栈中pop出一个符号,进行运算,将得到的结果入数栈,然后将当前的操作符入符号栈,如果当前的操作符优先级大于栈中的操作符,就直接入符号栈。
- 当表达式字符串扫描完毕了,就顺序的从数栈和符号栈中pop出相应的数和符号,并运算
- 最后在数栈只有一个数字,就是表达式的结果
图解
注意:图解理论如此,其实就是从右往左算,纯图解有bug,代码解决了bug
代码
注意这个简单的中缀计算器,不能计算小数,负数等
using System;using System.Collections.Generic;namespace ConsoleApp1{class Program{static void Main(string[] args){string expression = "3456-23/456-56+34-23/23-12+3";//定义两个栈,数栈和符号栈ArrayStack<int> numStack = new ArrayStack<int>(10);ArrayStack<string> operStack = new ArrayStack<string>(10);//定义需要的相关变量int index = 0;//表达式的索引int num1 = 0;//先出栈的数int num2 = 0;//后出栈的数string oper = "";//操作符int res = 0;//运算结果//扫描表达式while (true){//记录一下是数字还是操作符,因为index是引用的,这个r要多次用到,就要用变量存着string r = convertNumOper(ref index, expression);int stackpeak = numStack.peak();string operpeak = operStack.peak();if (isOper(r))//如果是运算符{//判断当前的符号栈是否为空if (!operStack.isEmpty()){//如果符号栈中有操作符,就进行比较,如果当前的操作符的优先级小于或者等于栈中的操作符,就需要从数栈中pop出两个数//再从符号站中pop出两个符号进行运算,将得到结果,入数栈,然后将当前的操作符入符号栈if (priority(r) <= priority(operStack.peak())){num1 = numStack.pop();num2 = numStack.pop();oper = operStack.pop();//前面一个操作符号是减法,如7-2-3,应该变成7-(2-(-3))if ((oper == "-" ||oper=="+")&& operStack.peak() == "-")num1 = 0 - num1;res = cal(num1, num2, oper);//把运算的结果如数栈numStack.push(res);//然后把当前的操作符入符号栈operStack.push(r);}else//如果当前操作符的优先级大于栈顶的操作符,就直接入符号栈{operStack.push(r);}}else//如果为空直接入栈{operStack.push(r);}}else//如果是数,则直接入数栈{numStack.push(Convert.ToInt32(r));}//index如果到了最后就退出whileif (index >= expression.Length)break;}//表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并运行while (true){//如果符号栈为空,则计算到最后的结果,数栈中只有一个数字【结果】if (operStack.isEmpty()){break;}num1 = numStack.pop();num2 = numStack.pop();oper = operStack.pop();//前面一个操作符号是减法,如7-2-3,应该变成7-(2-(-3))if ((oper == "-" || oper == "+") && operStack.peak() == "-")num1 = 0 - num1;res = cal(num1, num2, oper);//把运算的结果如数栈numStack.push(res);}//到这里数栈就一个数了,就是结果Console.WriteLine($"{expression}={numStack.pop()}");}//返回一个数字或者操作符的字符串static string convertNumOper(ref int index,string expression){string r = "";while (true){if (index >= expression.Length)break;if (expression[index] >= 48 && expression[index] <= 57)//数字{r += expression[index++];}else//是操作符{if (r != "")//如果r不等于空,说明前面有数字,现在到了操作符,为了保证r是纯数就退出了,index不++break;else//如果r等于空,这次调用方法index第一个指向符号,把符号取出来后,index++,就退出循环{r += expression[index++];break;}}}return r;}//返回运算符的优先级,优先级是人为确定的,优先级使用数字表示,数字越大则优先级越高static int priority(string oper){if (oper == "*" || oper == "/"){return 1;}else if (oper == "+" || oper == "-"){return 0;}return -1;//目前只有+-*/}//判断是不是一个运算符static bool isOper(string val){return val == "+" || val == "-" || val == "*" || val == "/";}//计算方法static int cal(int num1, int num2, string oper){int res = 0;//计算的结果switch (oper){//注意顺序,后传出来的数作为运算式子的第一个case "+": res = num2 + num1; break;case "-": res = num2 - num1; break;case "*": res = num2 * num1; break;case "/": res = num2 / num1; break;}return res;}}}
后缀表达式(逆波兰表达式)
和前缀表达式相似,只是运算符位于操作数之后,例如(3+4)*5-6对应的后缀表达式就是3 4 + 5 * 6 -
思路
从左至右扫描表达式,遇到数字时,就将数字压入栈中,遇到运算符时,pop两个数就行运算,得到结果又入栈,重复,直到最右端,最后运算出来的值就是表达式的结果
如上例子,从左往右扫,将3 ,4压入栈中
然后遇到了符号,pop两个数字,进行符号运算,得到结果入栈
注:num1是栈顶元素,num2是次顶元素,
num2 符号 num1
代码
using System;using System.Collections.Generic;namespace ConsoleApp1{class Program{static void Main(string[] args){//逆波兰表达式,数字和符号用空格隔开string expression = "3 4 + 5 * 6 -";ArrayStack<int> stack = new ArrayStack<int>(10);string[] exarr = expression.Split(" ");//直接分隔for(int i = 0; i < exarr.Length; i++){try{//如果转换成数字发生了异常就说明是符号stack.push(Convert.ToInt32(exarr[i]));}catch{//出两个数,计算结果,把结果入栈int num1 = stack.pop();int num2 = stack.pop();int result = cal(num1, num2, exarr[i]);stack.push(result);}}//最后栈中就剩一个值了,就是结果Console.WriteLine(stack.pop());}//计算方法static int cal(int num1, int num2, string oper){int res = 0;//计算的结果switch (oper){//注意顺序,后传出来的数作为运算式子的第一个case "+": res = num2 + num1; break;case "-": res = num2 - num1; break;case "*": res = num2 * num1; break;case "/": res = num2 / num1; break;}return res;}}}
前后缀表达式与中缀表达式区别
中缀表达式转成后缀表达式
思路
- 初始化两个栈:运算符栈s1和存储中间结果的栈s2
- 从左至右扫描中缀表达式
- 遇到操作数时,将其push入s2
- 遇到括号时:
- 如果是左括号,则直接push入s1
- 如果是右括号,则依次pop出s1栈顶的运算符,并push入s2,直到遇到左括号为止,此时将这一对括号丢弃
- 遇到运算符时,比较与s1顶运算符的优先级
- 如果s1为空,或栈顶运算符为左括号,则直接将此运算符入栈;
- 否则如果,当前运算符的优先级比栈顶运算符的高,也将运算符push入s1;
- 否则如果,当前运算符优先级<=栈顶的运算符的优先级,将s1栈顶的运算符pop出来,push到s2
- 再次来到5.a这一步,当前运算符与s1中新的栈顶的运算符相比较
- 重复步骤2至5,直到表达式的最右边
- 将s1中剩余的运算符依次弹出并压入s2
- 依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式
代码
中缀表达式转后缀表达式的核心方法InfixToSuffix```csharp using System; using System.Collections.Generic;
namespace ConsoleApp1 { class Program { static void Main(string[] args) {
//中缀表达式string infixExpression = "32+3*(7-2*(1+2))-3+12/4";//逆波兰表达式,数字和符号用空格隔开string suffixExpression = InfixToSuffix(infixExpression);ArrayStack<int> stack = new ArrayStack<int>(10);string[] exarr = suffixExpression.Split(" ");//直接分隔for (int i = 0; i < exarr.Length; i++){try{//如果转换成数字发生了异常就说明是符号stack.push(Convert.ToInt32(exarr[i]));}catch{//出两个数,计算结果,把结果入栈int num1 = stack.pop();int num2 = stack.pop();int result = cal(num1, num2, exarr[i]);stack.push(result);}}Console.WriteLine(infixExpression);Console.WriteLine(suffixExpression);//最后栈中就剩一个值了,就是结果Console.WriteLine(stack.pop());}//中缀表达式转换后缀表达式,传入一个中缀表达式static string InfixToSuffix(string expression){int index = 0;//扫描指针//1.初始化两个栈ArrayStack<string> s1 = new ArrayStack<string>(100);//符号栈ArrayStack<string> s2 = new ArrayStack<string>(100);//数字栈//2.从左往右扫描中缀表达式while (true){string r = convertNumOper(ref index, expression);//3.如果是操作数就直接push到s2if (!isOper(r)){s2.push(r);}//4.遇到括号时//4.a 如果是左括号,则直接push入s1else if (r == "("){s1.push(r);}//4.b 如果是右括号,则依次弹出s1栈顶的运算符,并push入s2,直到遇到左括号为止,此时将这一对括号丢弃else if (r == ")"){while (true){string a = s1.pop();if (a != "(")s2.push(a);elsebreak;}}//5.遇到运算符时,比较与s1顶运算符的优先级else{while (true){//5.a 如果s1为空,或者栈顶运算符为左括号,则直接将此运算符入栈if (s1.isEmpty() || s1.peak() == "("){s1.push(r);break;}//5.b 当前运算符的优先级比栈顶运算符的高,也将运算符push入s1else if (priority(r) > priority(s1.peak())){s1.push(r);break;}//5.c 当前运算符优先级<=栈顶的运算符的优先级,将s1栈顶的运算符pop出来,push到s2else if (priority(r) <= priority(s1.peak())){s2.push(s1.pop());}//5.d.i 再次来到5.a这一步,当前运算符与s1中新的栈顶的运算符相比较}}//6.重复2至5步,直到表达式的最右边if (index >= expression.Length)break;}//7.将s1中剩余的运算符依次弹出并压入s2while (true){if (s1.isEmpty())break;s2.push(s1.pop());}//8.依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式string suffixstr = "";while (true){if (s2.isEmpty())break;string t = s2.pop();suffixstr = t + " " + suffixstr;}return suffixstr.Trim();//去掉两端的空格}//返回一个数字或者操作符的字符串static string convertNumOper(ref int index, string expression){string r = "";while (true){if (index >= expression.Length)break;if (expression[index] >= 48 && expression[index] <= 57)//数字{r += expression[index++];}else//是操作符{if (r != "")//如果r不等于空,说明前面有数字,现在到了操作符,为了保证r是纯数就退出了,index不++break;else//如果r等于空,这次调用方法index第一个指向符号,把符号取出来后,index++,就退出循环{r += expression[index++];break;}}}return r;}//计算方法static int cal(int num1, int num2, string oper){int res = 0;//计算的结果switch (oper){//注意顺序,后传出来的数作为运算式子的第一个case "+": res = num2 + num1; break;case "-": res = num2 - num1; break;case "*": res = num2 * num1; break;case "/": res = num2 / num1; break;}return res;}//判断是不是一个运算符static bool isOper(string val){return val == "+" || val == "-" || val == "*" || val == "/"||val=="("||val==")";}//返回运算符的优先级,优先级是人为确定的,优先级使用数字表示,数字越大则优先级越高static int priority(string oper){if (oper == "*" || oper == "/"){return 1;}else if (oper == "+" || oper == "-"){return 0;}return -1;//目前只有+-*/}}
}
```



