栈与计算器实现

一、栈的介绍

1)栈(stack)是一个先进后出(FILO)的有序列表;
2)栈是限制线性表中的元素只能从一端插入和删除的特殊线性表,允许插入和删除的一端为栈顶,固定的一端为栈底。
在这里插入图片描述

二、栈的使用场景

1.子程序调用;
2.处理 递归调用;
3.表达式的转换(中缀表达式转后缀表达式)与计算;
4.二叉树遍历;
5.图形的深度优先(depth-first)搜索法

三、栈的入门

由于栈是一种线性有序列表,因此可以使用数组模拟栈的pop、push操作

1、实现思路分析:

  • 定义一个数组存放栈中的数据;
  • 定义一个top指针执行栈顶元素,默认为-1;
  • 入栈:先top++,让top指针向上移动一位,在将数据存放到指针所指的下标的位置;
  • 出栈:先取出top所指向的下标的数据,再top–,将top向下移动一位,最后将取出的数据返回;

2、代码实现

package stack;

import org.junit.Test;

import java.util.Scanner;

/**
 * 数组模拟栈
 * 栈的特点,从栈顶push,栈顶pop,固定的一端为栈底。
 */
public class ArrayStackDemo {

    public static void main(String[] args) {
        System.out.printf("开始测试");

        ArrayStack arrayStack = new ArrayStack(5);

        String key ="";
        Scanner scanner = new Scanner(System.in);
        boolean loop = true;
        while(loop){
            System.out.println("show:显示队列");
            System.out.println("push:向队列添加元素");
            System.out.println("pop :从队列取出元素");

            key = scanner.next();//接收一个字符
            switch (key) {
                case "show":
                    arrayStack.showStack();
                    break;
                case "push":
                    System.out.println("请输入要添加的数据");
                    int value = scanner.nextInt();
                    arrayStack.push(value);
                    break;
                case "pop":
                    try {
                        int getValue = arrayStack.pop();
                        System.out.printf("取出的数据是:%d\n", getValue);
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                default:
                    break;
            }
        }
        System.out.println("程序退出~~~");
    }


}

class ArrayStack{
    private int top= -1;//指定栈顶
    private int maxSize;
    private int[] stack;

    public ArrayStack(int maxSize) {
        this.maxSize = maxSize;
        this.stack = new int[maxSize];
    }

    /**
     * 栈满
     * @return
     */
    public boolean isFull(){
        return top==maxSize-1;
    }

    /**
     * 栈空
     * @return
     */
    public boolean isEmpty(){
        return top==-1;
    }
    /**
     * 向栈里添加数据-入栈-push
     * 先判断是否栈满,如果已满,则提示无法添加。
     */
    public void push(int value){
        if(isFull()){
            System.out.println("栈满~~~");
            return;
        }
        top++;
        stack[top]=value;
    }

    /**
     * 出栈
     * 先判断是否栈空,如果为空,则抛出一场,提示栈空。
     * @return
     */
    public int  pop(){
        if(isEmpty()){
           throw  new RuntimeException("栈空~~~");
        }
        int value = stack[top];
        top--;
        return value;
    }

    /**
     * 打印栈中的每个数据
     * 注意:出栈是从栈顶弹出
     */
    public void showStack(){
        if(isEmpty()){
            System.out.println("栈空~~~");
            return;
        }

        for(int i = top;i>-1;i--){
            int value = stack[i];
            System.out.println(value);
        }
    }
}

四、中缀表达式与后缀表达式

1、中缀表达式

像 “1+(2+3)*4” 这样便于人计算的表达式为中缀表达式;

中缀表达式实现计算器

package stack;

import com.sun.tools.corba.se.idl.InterfaceGen;

import java.util.Stack;

/**
 * 用栈计算中缀表达式,实现计算器功能
 */
public class Calculate {
    public static void main(String[] args) {
//        String expression = "30+2*6-2";
        String expression = "(5+2)*3-6*2+1";
        //1、创建两个栈,分别存放中缀表达式中的数和操作符。
        Stack<String> numStack = new Stack<String>();
        Stack<String> operStack = new Stack<String>();
        String keepNum = "";
        //2. 创建index变量,用于从左到右遍历中缀表达式。
        int index =0;
        while (true){
            //2.4 当index指针的值等于表达式长度,说明遍历结束
            if(index==expression.length()){
                break;
            }
            // 2.1 如果是一个数字,直接入numStack;
            char val = expression.substring(index,index+1).charAt(0);
            // 2.1 如果是一个数字,直接入numStack;
            if(!isOper(val)){
                //解决多位数运算问题:
                //当遍历到一个字符,发现它是一个数字,则继续看看后一位,如果后一位还是数字,就将这个数组拼接在前一个数字后面并继续往后看,直到后一位是操作符为止。
                //将最终拼接的数字字符串 push 到栈中.
                keepNum = keepNum+val;
                if(index == expression.length()-1){
                    numStack.push(keepNum);
                }else{
                    if(isOper(expression.substring(index+1,index+2).charAt(0))){
                        numStack.push(keepNum);
                        keepNum="";
                    }
                }

            }else{
                //2.2 如果是操作符,判断operStack栈中是否为空
                //2.2.1 如果不是空,判断当前操作符的优先级是否<=operStack栈中的操作符
                if(!operStack.empty()){
                    String oldOper = operStack.peek();//注意此处只是看一眼,没有真正取出
                    //2.2.1.1 如果当前操作符的优先级<=operStack栈中的操作符优先级
                    if(getPriority(val)<=getPriority(oldOper.charAt(0))){
                        // 1)从numStack弹出两个数,从operStack栈弹出一个数,并运算;
                        int num1 = Integer.parseInt(numStack.pop()) ;
                        int num2 = Integer.parseInt(numStack.pop());
                        int res = calculate(num1,num2,operStack.pop().charAt(0));
                        // 2)运算结果 push 到 numStack;
                        numStack.push(""+res);
                        // 3)将当前新的操作符 push到operStack;
                        operStack.push(""+val);
                    }else{
                        //2.2.1.2 如果当前操作符的优先级>operStack栈中的操作符优先级,直接入operStack栈
                        operStack.push(""+val);
                    }
                }else{
                    operStack.push(""+val);
                }
            }
            //2.3 index++,让index指针向后移动。
            index++;
        }

        //3. 第一轮遍历完表达式后,operStack栈中剩下的操作符优先级都相同,按顺序遍历两个栈(循环以下操作)
        while(true){
            //3.3 当operStack中为空,说明计算结束
            if(operStack.empty()){
               break;
            }
            //3.1 从numStack中弹出两个数,从operStack栈弹出一个数,并运算;
            char oper = operStack.pop().charAt(0);
            int num1 = Integer.parseInt(numStack.pop()) ;
            int num2 = Integer.parseInt(numStack.pop());
            int res = calculate(num1,num2,oper);
            //3.2 运算结果 push 到 numStack;
            numStack.push(""+res);
        }
        //3.4 返回 numStack 中唯一的一个数,即为当前表达式最终运算结果
        int finalRes = Integer.parseInt(numStack.pop());
        System.out.println("最终计算结果为:"+finalRes);

        //1、创建两个栈,分别存放中缀表达式中的数和操作符。
        //2. 创建index变量,用于从左到右遍历中缀表达式。
        //2.1 如果是一个数字,直接入numStack;(注意多位数的处理)

        //2.2 如果是操作符,判断operStack栈中是否为空
        //2.2.1 如果不是空,判断当前操作符的优先级是否<=operStack栈中的操作符
        //2.2.1.1 如果当前操作符的优先级<=operStack栈中的操作符优先级
        //》》》1)从numStack弹出两个数,从operStack栈弹出一个数,并运算;
        //》》》2)运算结果 push 到 numStack;
        //》》》3)将当前新的操作符 push到operStack;
        //2.2.1.2 如果当前操作符的优先级>operStack栈中的操作符优先级,直接入operStack栈

        //2.2.2 如果是空,直接入operStack栈

        //2.3 index++,让index指针向后移动。
        //2.4 当index指针的值等于表达式长度,说明遍历结束

        //3. 第一轮遍历完表达式后,operStack栈中剩下的操作符优先级都相同,按顺序遍历两个栈(循环以下操作)
        //3.1 从numStack中弹出两个数,从operStack栈弹出一个数,并运算;
        //3.2 运算结果 push 到 numStack;
        //3.3 当operStack中为空,说明计算结束
        //3.4 返回 numStack 中唯一的一个数,即为当前表达式最终运算结果
    }

    public static boolean isOper(char val){
        return val=='+' || val=='-' ||val=='*'||val=='/' ;
    }

    public static int getPriority(char val){
        if(val=='(' || val==')'){
            return 2;
        }else if(val=='*' || val=='/'){
            return 1;
        }else if(val=='+' || val=='-'){
            return 0;
        }else{
            return -1;
        }
    }

    public static int calculate(int num1,int num2,int 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;
            default:
                break;
        }
        return res;

    }
}

2、后缀表达式(逆波兰表达式)

像 “1 2 3 + 4 * +” 这样便于计算机使用的表达式为后缀表达式,后缀表达式的特点在于运算符在数字后。

3、中缀表达式转后缀表达式

1)初始化两个栈:存放运算符的栈 s1 和存放中间结果的栈 s2;
2)从左到右扫描中缀表达式;
3)如遇到操作数时:直接将其push到s2;
4)如遇到操作符时:要比较当前运算符与s1栈顶元素操作符的优先级:
(1)如果s1为空,或s1栈顶元素为“(” 时,直接将运算符push到s1;
(2)否则,如果当前运算符的优先级大于栈顶运算符的优先级,直接将运算符push到s1;
(3)否则(运算符优先级小于等于栈顶运算符优先级),先将s1栈顶运算符弹出,并push到s2中,然后再从4-1开始将当前运算符与新的栈顶运算符比较优先级。
5)如遇到小括号时:
(1)如果是“(”左括号,直接push到s1中;
(2)如果是“)”右括号,则将s1中的运算符pop出,并push到s2中,直到遇到一个“(”左括号为止,将这一对括号丢弃。
6)重复3至5步骤,直到将中缀表达式遍历完毕。
7)最后将s1中剩余的运算符一次pop出并push进s2中;
8)依次弹出s2中的元素,并输出,结果的逆序即为中缀表达式对应的后缀表达式。

中缀表达式转后缀表达式、并实现计算器功能

package stack;

import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;

/**
 * 后缀表达式(逆波兰表达式)实现逆波兰计算器
 *
 * 本篇最终实现功能:
 * 给一个中缀表达式,将其转换成后后缀表达式并求计算结果。
 */
public class PolandNotation {

    @Test
    public void testParseToSufixExpress() {
        String expression = "1+((2+3)*4)-5";
        List<String> list = parseToSufixExpress(expression);
        for(String str : list){
            System.out.printf(str);
        }

    }

    public static void main(String[] args) {
        //(3+4)*5-6 -> 30 4 + 5 * 6 -

//        //1、(改造前)逆波兰表达式的数字、符号之间用空格格开。将其转换成集合
//        String expression = "3 4 + 5 * 6 -";
//        List<String> list = convertToList(expression);

        //1、(改造后)给一个中缀表达式,转成后缀表达式对应的list
        String expression = "1+((2+3)*4)-5";
        List<String> list = parseToSufixExpress(expression);
        //2、创建一个栈,用于存放表达式里遍历出来的数
        Stack<String> numStack = new Stack<String>();
        int index = 0;//用于遍历表达式

        //3、遍历list集合,判断当前的元素是数字还是操作符
        for(String str : list){
            if(!str.matches("\\d+")){
                //3.1 如果是符号
                // 则从numStack 中pop出两个数,并用当前运算符计算。
                int num1 =Integer.parseInt(numStack.pop());
                int num2 =Integer.parseInt(numStack.pop());
                int res = calculate(num1,num2,str.charAt(0));
                // 将计算结果存入numStack 中;
                numStack.push(""+res);
            }else{
                //3.2 如果是数字入numStack;
                numStack.push(str);
            }
        }

        int finalRes = Integer.parseInt(numStack.pop());
        System.out.println("最终计算结果为:"+finalRes);
    }

    /**
     * 将中缀表达式转成后缀表达式
     * 从左到右遍历中缀表达式,为了便于遍历,先将中缀表达式转成list。
     *
     * 创建一个栈 s1 用于存放中缀表达式集合中的运算符和左括号,创建一个list集合用于存放中间结果
     * 从 sufixList 中遍历每一个元素:
     * 1)如果是数字,直接添加到list集合中,
     * 2)如果是运算符
     * 2.1 如果s1栈中为空,或栈顶元素为左括号,直接将符号压入栈中;
     * 2.2 否则,如果运算符的优先级高于栈顶运算符,直接将符号压入栈中;
     * 2.3 否则,如果运算符的优先级小于等于栈顶运算符,将s1栈顶元素弹出,存到list中,并继续与下一个栈顶元素比较。
     * 3)如果是括号:
     * 3.1 如果是左括号,直接压入s1栈中;
     * 3.2 如果是右括号,将s1 中的栈顶元素弹出,存到list中,直到遇到一个左括号,舍弃这一对括号。
     *
     * 4)循环遍历list,直到表达式list遍历完,最后将s1 栈中的元素,依次从栈顶弹出,存到list中,最后list中的元素顺序输出,就是后缀表达式了。
     *
     * @param expression 中缀表达式
     * @return
     */
    public static List<String>  parseToSufixExpress(String expression){//expression = 1+((2+3)*4)-5
        //从左到右遍历中缀表达式,为了便于遍历,先将中缀表达式转成list。
        List<String> sufixList = parseToSufixList(expression);
        Stack<String> s1 = new Stack<String>();
        List<String> s2 = new ArrayList<String>();
        for(String str : sufixList){
            if(str.matches("\\d+")){
                s2.add(str);
            }else if("(".equals(str)){
                s1.push(str);
            }else if(")".equals(str)){
                while(!s1.peek().equals("(")){
                    String value = s1.pop();
                    s2.add(value);
                }
                s1.pop();//弹出与有括号匹配的这个左括号;
            }else{ //运算符
               /*
                * 2.1 如果s1栈中为空,或栈顶元素为左括号,直接将符号压入栈中;
                * 2.2 否则,如果运算符的优先级高于栈顶运算符,直接将符号压入栈中;
                * 2.3 否则,如果运算符的优先级小于等于栈顶运算符,将s1栈顶元素弹出,存到list中,并继续与下一个栈顶元素比较。
                */

                if (s1.size()!=0 && !"(".equals(s1.peek()) && getPriority(str.charAt(0))<= getPriority(s1.peek().charAt(0))){
                    s2.add(s1.pop());
                    s1.push(str);
                }else{
                    s1.push(str);
                }

            }
        }
        while(s1.size()>0){
           s2.add(s1.pop());
        }
        return s2;
    }

    /**
     * 将中缀表达式字符串转成对应的list
     * 分析:
     * 中缀表达式中可能存在多位数,因此要区分情况加入list
     * 1)如果不是数字(运算符或括号)则直接加入list;
     * 2)如果是数字,则要循环遍历表达式,向后找,将找到的数都拼起来,直到找到一个非数字的字符,结束遍历。
     * @param expression
     * @return
     */
    public static List<String>  parseToSufixList(String expression){
        List<String> list= new ArrayList<String>();
        int i=0;
        String str ;//用于拼接多位数
        char ch =' ';//用于接收从表达式中遍历到的字符
        do{
            if((ch=expression.charAt(i))<48 || (ch=expression.charAt(i))>57){ //1)如果不是数字(运算符或括号)则直接加入list;
                list.add(""+ch);
                i++;
            }else{ //2)如果是数字,则要循环遍历表达式,向后找,将找到的数都拼起来,直到找到一个非数字的字符,结束遍历。
                str = "";
                while(i<expression.length() && (ch=expression.charAt(i))>=48 && (ch=expression.charAt(i))<=57){
                    str = str+ch;
                    i++;
                }
                list.add(str);
            }

        }while(i<expression.length());

        return list;
    }

    public static List<String> convertToList(String expression){
        String[] vals = expression.split(" ");
        List<String> list = Arrays.asList(vals);
        return list;

    }


    public static int calculate(int num1,int num2,int 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;
            default:
                break;
        }
        return res;

    }


    public static int getPriority(char val) {
        if (val == '(' || val == ')') {
            return 2;
        } else if (val == '*' || val == '/') {
            return 1;
        } else if (val == '+' || val == '-') {
            return 0;
        } else {
            return -1;
        }
    }
}

五、逆波兰计算器完整功能

package stack;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
import java.util.regex.Pattern;

/**
 * @author zhangxiaojuan@baoyinxiaofei.com
 * @create 2020-07-28 7:33 PM
 */
public class ReversePolishMultiCalc {
    //匹配 + - * / ( ) 运算符
    static final String SYMBOL = "\\+|-|\\*|/|\\(|\\)";
    static final String LEFT="(";
    static final String RIGHT=")";
    static final String ADD="+";
    static final String MINUS="-";
    static final String TIMES="*";
    static final String DIVISION="/";

    //加减+ - 的优先级
    static final int LEVEL_01 = 1;
    //乘除 * / 的优先级
    static final int LEVEL_02 = 2;
    //括号
    static final int LEVEL_HIGH = Integer.MAX_VALUE;

    //用于存放运算符
    static Stack<String> stack = new Stack<String>();
    //用于存放中间过程
    static List<String> data = Collections.synchronizedList(new ArrayList<String>());


    /**
     * 去除所有空白符
     * \\s+ 匹配任何空白字符,包括空格、制表符、换页符等等,等价于[ \f\n\t\r\v]
     * @param s
     * @return
     */
    public static String replaceAllBlank(String s){
        return s.replaceAll("\\s+","");
    }

    /**
     * 判断是否为数字
     * @param s
     * @return
     */
    public static boolean isNumber(String s){
        Pattern pattern = Pattern.compile("^[-\\+]?[.\\d]*$");
        return pattern.matcher(s).matches();
    }

    /**
     * 判断是否是运算符
     * @param s
     * @return
     */
    public static boolean isSymbol(String s){
        return s.matches(SYMBOL);
    }

    /**
     * 匹配运算等级
     * @param s
     * @return
     */
    public static int calcLevel(String s){
        if("+".equals(s) || "-".equals(s)){
            return LEVEL_01;
        }else if("*".equals(s) || "/".equals(s)){
            return LEVEL_02;
        }
        return LEVEL_HIGH;
    }

    /**
     * 将中缀表达式转换成后缀表达式对应的list集合
     * @param s
     * @return
     */
    public static List<String> doMatch(String s){
        if(s==null || "".equals(s.trim())) throw new RuntimeException("data is empty");
        if(!isNumber(s.charAt(0)+"")) throw new RuntimeException("data illeagle,start not with a number");
        s = replaceAllBlank(s);
        String each;
        int start = 0;
        for(int i=0;i<s.length();i++){
            if(isSymbol(s.charAt(i)+"")){
               each = s.charAt(i)+"";
               //栈为空,当前操作符是左括号,或者操作符优先级大于栈顶优先级&&操作符不是右括号,这三种情况时,直接入栈
               if(stack.isEmpty() || LEFT.equals(each) || ((calcLevel(each)>calcLevel(stack.peek())) && calcLevel(each)<LEVEL_HIGH) ){
                   stack.push(each);
               }else if(!stack.isEmpty() && calcLevel(each)<= calcLevel(stack.peek())){
                   //如果栈非空,且当前运算符的优先级小于栈顶运算符的优先级,则从栈中弹出栈顶运算符并将其存入集合中,直到栈为空或者遇到一个左括号"("为止,最后将当前的运算度each存到栈中。
                   while (!stack.isEmpty() && calcLevel(each)<= calcLevel(stack.peek())){
                       if(calcLevel(stack.peek()) == LEVEL_HIGH){
                           break;
                       }
                       data.add(stack.pop());
                   }
                   stack.push(each);
               }else if(RIGHT.equals(each)){
                   //如果当前操作符是右括号")",则依次出栈并将其存入集合中,直到栈为空或者遇到一个左括号"("为止,此时弹出左括号
                   while (!stack.isEmpty() && LEVEL_HIGH >= calcLevel(stack.peek())){
                       if(calcLevel(stack.peek()) == LEVEL_HIGH){ //是左括号时弹出左括号并结束循环
                           stack.pop();
                           break;
                       }
                       data.add(stack.pop());
                   }
               }
               start=i; //start指向前一个运算符的下标位置,i指向当前遍历到的字符的下标位置
            }else if(i==s.length()-1 || isSymbol(s.charAt(i+1)+"")){//如果不是运算符(即当前是一个数字)已经遍历到表达式的最后一个字符,或者当前字符的下一个字符是运算符时,直接将当前字符入栈
                each = start ==0? s.substring(start,i+1):s.substring(start+1,i+1);//从整个表达式串中截取当前的数字(从上一个运算符的位置开始,到当前遍历到的位置为止均是此次数字要截取的内容,此处考虑了多位数的情况)
                if(isNumber(each)){
                    data.add(each);
                    continue;
                }
                throw new RuntimeException("data not match number");
            }
        }

        //遍历完整个表达式之后,如果栈里还有表达式,则将其依次出栈入集合(即从栈顶出栈并添加到集合中,可以将栈反转,并将其转换成list,最后将整个集合添加到集合中)
        Collections.reverse(stack);
        data.addAll(new ArrayList<String>(stack));
        System.out.println(data);
        return data;
    }


    /**
     * 计算结果
     * @param list 后缀表达式对应的list集合
     * @return
     */
    public static Double doCal(List<String> list){
        Double d = 0d;
        if(list ==null || list.isEmpty()){
            return null;
        }
        if(list.size() ==1){
            System.out.println(list);
            d=Double.valueOf(list.get(0));
            return d;
        }
        ArrayList<String> list1 = new ArrayList<String>();
        for(int i=0;i<list.size();i++){
            list1.add(list.get(i));
            if(isSymbol(list.get(i))){
                Double d1 = doTheMath(list.get(i-2),list.get(i-1),list.get(i));
                list1.remove(i);
                list1.remove(i-1);
                list1.set(i-2,d1+"");
                list1.addAll(list.subList(i+1,list.size()));
                break;
            }
        }
        doCal(list1);
        return d;
    }


    public static Double doTheMath(String s1,String s2,String symbol){
        Double result;
        switch (symbol){
            case ADD:result = Double.valueOf(s1) + Double.valueOf(s2); break;
            case MINUS:result = Double.valueOf(s1) - Double.valueOf(s2); break;
            case TIMES:result = Double.valueOf(s1) * Double.valueOf(s2); break;
            case DIVISION:result = Double.valueOf(s1) / Double.valueOf(s2); break;
            default:result=null;
        }
        return result;
    }

    public static void main(String[] args) {
//        String math = "9+(3-1)*3+10/2";
        String math ="12.8+(2-3.55)*4+10/5.0";
        doCal(doMatch(math));
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值