前缀表达式
前缀表达式是一种没有括号的算术表达式,与中缀表达式不同的是,其将运算符写在前面,操作数写在后面。前缀表达式也称为“波兰表达式”。例如,- 1 + 2 3,它等价于1-(2+3)。
求值方法
从右至左扫描表达式,若当前字符是数字则一直到数字串的末尾再记录下来,若为运算符,则将右边离得最近的两个“数字串”作相应运算,然后以此作为一个新的“数字串”并记录下来;扫描到表达式最左端时扫描结束,最后运算的值即为表达式的值。
例如:对前缀表达式“- 1 + 2 3”求值,扫描到3时,记录下这个数字串,扫描到2时,记录下这个数字串,当扫描到+时,将+右移做相邻两数字串的运算符,记为2+3,结果为5,记录下5这个新数字串,然后继续向左扫描,扫描到1时,记录下这个数字串,扫描到-时,将-右移做相邻两数字串的运算符,记为1-5,结果为-4,此时关于这个表达式的全部运算已完成,故表达式的值为-4。
中缀表达式
运算符在表达式中间,就是日常的算式形式。比如1+2*3
求值方法
此时,中缀表达式写为一个字符串,返回最后的结果数字。
思路:
创建两个栈,一个用来存放数字,一个用来存放运算符。
通过一个index指针遍历字符串。
如果该符号是数字,考虑多位数的情况,如果是最后一位,直接加入数栈,如果不是最后一位且下一位是数字,进行字符串的拼接,如果不是最后一位且下一位是运算符,将字符串加入数栈。
如果该符号是运算符。如果运算符栈为空,直接加入运算符栈,如果不为空,当该符号的优先级大于运算符栈顶的符号时,直接加入运算符栈,当该符号的优先级小于等于运算符栈顶的符号时,从数栈pop出两个数,从运算符栈pop出一个运算符,计算后,结果加入数栈,该符号加入运算符栈。
遍历结束后,依次从数栈和运算符栈中pop元素运算,直到数栈只剩下一个数,或运算符栈空。此时剩下的数为结果。
代码实现
package DataStructure.Stack;
/**中缀
* 综合计算器
*/
public class calculator {
public static void main(String[] args) {
String str="0-5-5-5";
//数字栈
ArrayStack2 numStack=new ArrayStack2(10);
//字符栈
ArrayStack2 operStack=new ArrayStack2(10);
//用一个index挨个扫描str
int index=0;
int num1=0;
int num2=0;
int oper=0;
int res=0;
char ch=' ';
String s="";
while (true){
ch=str.substring(index,index+1).charAt(0);
//判断ch是不是运算符,是运算符
if(operStack.isOper(ch)){
//栈空,直接加入
if(operStack.isEmpty()){
operStack.push(ch);
}else {
//栈不空,判断优先级,大于栈顶的直接加入
if (operStack.youxian(ch)>operStack.youxian(operStack.peek())){
operStack.push(ch);
}else {
//小于等于时,从数字栈弹两个数字,符号栈弹一个符号,运算完结果存数字栈,符号存符号栈
num1=numStack.pop();
num2=numStack.pop();
oper=operStack.pop();
res=operStack.cal(num1,num2,oper);
numStack.push(res);
operStack.push(ch);
}
}
}else {
//是数字,扫描下一位,如果是符号就加入栈,否则进行拼接
s+=ch;
//是不是最后一位
if(index==str.length()-1){
numStack.push(Integer.parseInt(s));
s="";
}else{
if(operStack.isOper(str.substring(index+1,index+2).charAt(0))){
numStack.push(Integer.parseInt(s));
s="";
}
}
}
index++;
//index=字符串长度,说明扫描完了
if(index>str.length()-1){
break;
}
}
//扫描完后,不断出栈,直到符号栈空
while (true){
if(operStack.isEmpty()){
break;
}else {
num1=numStack.pop();
num2=numStack.pop();
oper=operStack.pop();
res=operStack.cal(num1,num2,oper);
numStack.push(res);
}
}
System.out.println(str+"="+numStack.pop());
}
}
class ArrayStack2{
//容量
public int max;
public int[] stack;
//栈顶
public int top=-1;
public ArrayStack2(int max){
this.max=max;
stack=new int[this.max];
}
//空
public boolean isEmpty(){
return top==-1;
}
//满
public boolean isFull(){
return top==max-1;
}
//入栈
public void push(int num){
if(isFull()){
System.out.println("栈满");
return;
}else {
top++;
stack[top]=num;
}
}
//出栈
public int pop(){
if(isEmpty()){
throw new RuntimeException("空栈");
}else {
int value=stack[top];
top--;
return value;
}
}
//展示
public void show(){
if(isEmpty()){
System.out.println("栈空");
}else {
for (int i = top; i >=0; i--) {
System.out.printf("stack[%d]:%d\n",i,stack[i]);
}
}
}
//返回栈顶
public int peek(){
return stack[top];
}
//判断运算符的优先级,只支持加减乘除
public int youxian(int oper){
if(oper=='*' || oper=='/'){
return 1;
}else if(oper=='+' ||oper=='-'){
return 0;
}else {
return -1;
}
}
//运算
public int cal(int num1,int num2,int oper){
int res=0;
switch (oper){
case '*':
res=num1*num2;
break;
case '/':
res=num2/num1;
break;
case '+':
res=num1+num2;
break;
case '-':
res=num2-num1;
break;
default:
break;
}
return res;
}
//判断是不是运算符
public boolean isOper(char ch){
return ch=='*'||ch=='/'||ch=='+'||ch=='-';
}
}
后缀表达式
将运算符写在操作数之后的一种表达式。也称为“逆波兰表达式”。对电脑来说后缀表达式更好运算,因此一般是把中缀表达式转化成后缀,然后计算。
中缀表达式转后缀表达式
思路:
首先将中缀表达式字符串转为list方便之后的运算。依次扫描字符串,如果是运算符,直接加入list,如果是数字,考虑多位数,当下一位是数字时,进行字符串的拼接,当下一位是运算符时,把字符串加入list。
//字符串转化为list
public static List<String> getList(String exp){
int i=0;
String str;
List<String> list=new ArrayList<String>();
do {
char item=exp.charAt(i);
if(item<48 || item>57){
list.add(item+"");
i++;
}else {
str="";
//如果是数,考虑多位数
while(i<exp.length() && exp.charAt(i)>=48 && exp.charAt(i)<=57){
str+=exp.charAt(i);
i++;
}
list.add(str);
}
}while (i<exp.length());
return list;
}
遍历中缀表达式list。创建两个栈s1,s2。s2也可以用数组代替,因为s2只有加入的操作。
当是数字时,直接加入s2。
当是运算符时,如果s1是空的或者s1栈顶是左括号(,直接把该符号加入s1;否则,判断优先级,如果该符号的优先级大于s1栈顶符号的优先级,把该符号加入s1,如果该符号的优先级小于等于s1栈顶符号的优先级,把s1栈顶运算符弹出加入s2,并重新判断优先级。
当是左括号(时,直接加入s1.
当是右括号)时,把s1栈顶运算符弹出加入s2,直到遇到左括号(,之后把左括号弹出。
遍历结束后,将s1元素依次弹出加入s2.
最后s2倒序即为后缀表达式。
代码实现
//中缀表达式转为后缀
public static List<String> toHou(List<String> list){
//创建一个符号栈,一个结果栈(方便输出改为数组)
Stack<String> s1=new Stack<String>();
List<String> s2=new ArrayList<>();
//遍历前缀表达式
for (String item:list) {
//是数字,直接加入s2
if(item.matches("\\d+")){
s2.add(item);
}else if(item.equals("(")){//是(直接加入s1
s1.push(item);
}else if(item.equals(")")){
//如果是右括号“)”,则依次弹出 s1 栈顶的运算符,并压入 s2,直到遇到左括号为止,此时将这一对括号丢弃
while (!s1.peek().equals("(")){
s2.add(s1.pop());
}
s1.pop();
}else{//是运算符
//当 item 的优先级小于等于 s1 栈顶运算符, 将 s1 栈顶的运算符弹出并加入到 s2 中,再次转到(4.1)与 s1 中新的栈顶运算符相比较
while (!s1.empty() && !s1.peek().equals("(")&& youxian(s1.peek())>=youxian(item)){
s2.add(s1.pop());
}
s1.push(item);
}
}
//将 s1 中剩余的运算符依次弹出并加入 s2
while (!s1.empty()) {
s2.add(s1.pop());
}
return s2;
}
//判断运算符优先级
public static int youxian(String s){
int res=0;
switch (s){
case "+":
res= 1;
break;
case "-":
res= 1;
break;
case "*":
res= 2;
break;
case "/":
res= 2;
break;
default:
break;
}
return res;
}
后缀表达式的运算
创建一个栈。遍历后缀表达式。
如果是数字,直接压入栈。
如果是运算符,从栈中pop出两个元素,进行运算(顺序为后一个对前一个),将结果压入栈中。
遍历结束后,栈中剩下的一个数字就是结果。
代码实现
//运算后缀表达式
public static int cal(List<String> exp){
Stack<String> stack=new Stack<>();
int num1;
int num2;
for (String s:exp) {
if(s.matches("\\d+")){
stack.push(s);
}else {
num1=Integer.parseInt(stack.pop());
num2=Integer.parseInt(stack.pop());
int res=0;
if(s.equals("+")){
res=num1+num2;
}else if(s.equals("-")){
res=num2-num1;
}else if(s.equals("*")){
res=num2*num1;
}else if(s.equals("/")){
res=num2/num1;
}else {
throw new RuntimeException("操作符错误");
}
stack.push(res+"");
}
}
return Integer.parseInt(stack.pop());
}