栈结构应用_普通计算器和逆波兰计算器

栈的运用--普通计算器和逆波兰计算器

 用栈实现计算器,这个比较难。有兴趣和热情的朋友,你可以看看。没有的,建议你看下一个数据结构。

逆波兰计算器是我们的终极目标,在实现目标之前,需要不断积累,所以我们一步步来:

普通单位加减乘除计算器-->普通多位加减乘除计算器-->前中后缀表达式规则-->逆波兰计算器

单位加减乘除实现:输入一个字符串“2+2*5-2”,通过栈计算出答案

算法原理:

栈结构,我选取上面实现的链表模拟栈;在原有基础上,新增 isEmpty,getTop(获取栈顶),priority(获取操作符的优先级),isOper(判断是否为运算符),cal(输入两个数字与一个运算符进行数学运算)方法,具体实现见代码;

创建numStick用于存储数字,创建operStick用于存储运算符,对字符串进行逐项扫描,每扫描一个字符判断其是否为运算符(+-*/)

是运算符时,就判断operStick空吗(isEmpty实现)?空就直接入栈,不空就要进行当前运算符与栈顶运算符(用getTop获取)进行优先级的比较(priority实现)

当前<=top,则将numStick中的两个栈点进行出栈,将operStick的栈顶进行出栈,这些出栈数据统统装入cal方法进行运算,运算结果放入numStick中,当前运算符入栈。当前>top,当前运算符直接入栈

不是运算符时,就直接入numStick栈;在上述判断执行完后,继续循环执行,直到字符串中所有字符都被扫描即可;

扫描完成后,所有数据被列入栈中,上述操作的目的旨在将高级运算在入栈时,运算干净,这样栈里都是同级的运算

下面对栈中的同级运算数据进行运算,运算过程就是numStick出两个,operStick出一个,进入cal方法中运算,结果继续入numStick栈,然后接着重复计算,直到operStack中没有运算符残留,就结束。

那么numStack中一定只剩下一个数字,而它就是我们苦苦寻找的结果!

package cn.dataStructure.demo.stack.calculator;

class DoubleNode{
    public int data;
    public DoubleNode next;//保存下一个结点
    public DoubleNode pre;//保存上一个结点
    public DoubleNode(int data){
        this.data =data;
    }
    @Override
    public String toString() {
        return "["+this.data +"]";
    }
}
class DoubleLinkedListStack{
    private DoubleNode root=new DoubleNode(0);//创建有头根节点
    public DoubleNode getRoot(){
        return this.root;
    }
    public void push(int data){//普通add:不按序号存储
        DoubleNode temp=this.root;//root不能动,需要辅助结点
        while (true){//查找链表最后结点
            if (temp.next==null){
                break;
            }
            temp=temp.next;//向后一位
        }
        DoubleNode newNode=new DoubleNode(data);
        temp.next=newNode;//尾结点双向添加
        newNode.pre=temp;
    }
    public int pop(){
        if (root.next==null){//判断是否为空链表
            throw new RuntimeException("空栈");
        }
        DoubleNode temp=root;
        while (temp.next!=null){//遍历到链表最后
            temp=temp.next;
        }
        int value=temp.data;
        temp.pre.next=null;//自我删除
        return value;
    }
    public void show(){//逆向打印
        if (root.next==null){//判断是否为空链表
            System.out.println("空栈");
            return;
        }
        DoubleNode temp=root;
        while (temp.next!=null){//遍历到链表最后
            temp=temp.next;
        }
        while (temp!=root){//逆向打印
            System.out.println(temp);
            temp=temp.pre;
        }
    }
    //判断栈空
    public boolean isEmpty(){
        return root.next==null;
    }
    //返回栈顶的值
    public int getTop(){
        if (root.next==null){//判断是否为空链表
            throw new RuntimeException("空栈");
        }
        DoubleNode temp=root;
        while (temp.next!=null){//遍历到链表最后
            temp=temp.next;
        }
        return temp.data;
    }
    //返回运算符优先级,正比
    public int prioprity(int oper){
        if (oper=='*'||oper=='/'){
            return 1;
        }else if (oper=='+'||oper=='-'){
            return 0;
        }else {
            return -1;//暂且支持加减乘除
        }
    }
    //判断是否为加减乘除
    public boolean isOper(int oper){
        return oper=='+'||oper=='-'||oper=='/'||oper=='*';
    }
    //对数据进行计算
    public int cal(int num1,int num2,int oper){
        int result=0;//存储结果
        switch (oper){
            case '+':
                result=num1+num2;
                break;
            case '-'://栈先入后出,所以2-1
                result=num2-num1;
                break;
            case '*':
                result=num1*num2;
                break;
            case '/'://栈先入后出,所以2-1
                result=num2/num1;
                break;
        }
        return result;
    }
}
public class 栈实现计算器 {
    public static void main(String[] args) {
        DoubleLinkedListStack numStack=new DoubleLinkedListStack();//数字栈
        DoubleLinkedListStack operStack=new DoubleLinkedListStack();//运算符栈
        String str="2+5*2-2";//10
        int index=0;//对str进行扫描的索引
        char ch=' ';//保存每次扫描到的字符
        int num1;//存储要计算的数字
        int num2;
        int value;//用于承接临时计算数据
        //对字符串进行扫描,将所有数据录入栈中
        while (true){
            ch=str.substring(index,index+1).charAt(0);
            if (operStack.isOper(ch)){//判断是否为操作符
                if (operStack.isEmpty()){//操作符栈空时,扫描到的操作符直接入栈
                    operStack.push(ch);
                }else if (operStack.prioprity(ch)<=operStack.prioprity(operStack.getTop())){//判断当前操作符的优先级<=栈顶操作符的优先级
                    num1=numStack.pop();
                    num2=numStack.pop();
                    value=operStack.cal(num1,num2,operStack.pop());
                    numStack.push(value);
                    operStack.push(ch);
                }else{//当前操作符的优先级>栈顶操作符的优先级
                    operStack.push(ch);
                }
            }else {
                numStack.push(ch-48);//数字是按char接收的,所以要减去48,从char恢复到int
            }
            index++;
            if (index==str.length()){//此时字符串中所有数据被扫描完
                break;
            }
        }
        //对栈中数据进行计算
        while (true){
            if (operStack.isEmpty()){//当操作符栈空,数据计算完成
                break;
            }
            num1=numStack.pop();
            num2=numStack.pop();
            value=operStack.cal(num1,num2,operStack.pop());
            numStack.push(value);
        }
        System.out.printf("%s=%d",str,numStack.pop());
    }
}
2+5*2-2=10

开心了吗?其实还是有点不开心,因为上面这个只能是一位数字运算,多位数程序就崩了,原因在于我们在数字入栈前没有判断下一个字符是不是数字。那么我们开始着手升级

 多位加减乘除实现:输入一个字符串“2+2*5-20”,通过栈计算出答案

算法分析:

修改字符串

String str="2+5*2-20";//-8

 新增number用于处理多位计算,将多次读入的字符合并

String number="";//用于处理多位计算,将多次读入的字符合并

 对数字入栈进行升级

//对字符串进行扫描,将所有数据录入栈中
        while (true){
            ch=str.substring(index,index+1).charAt(0);
            if (operStack.isOper(ch)){//判断是否为操作符
                if (operStack.isEmpty()){//操作符栈空时,扫描到的操作符直接入栈
                    operStack.push(ch);
                }else if (operStack.prioprity(ch)<=operStack.prioprity(operStack.getTop())){//判断当前操作符的优先级<=栈顶操作符的优先级
                    num1=numStack.pop();
                    num2=numStack.pop();
                    value=operStack.cal(num1,num2,operStack.pop());
                    numStack.push(value);
                    operStack.push(ch);
                }else{//当前操作符的优先级>栈顶操作符的优先级
                    operStack.push(ch);
                }
            }else {
                //对下面这句话进行修改,实现多位运算支持
                //numStack.push(ch-48);//数字是按char接收的,所以要减去48,从char恢复到int
                number+=ch;//合并所有扫描到的数字
                if (index==str.length()-1){//若该数字是最后一位,无需判断直接入栈
                    numStack.push(Integer.parseInt(number));
                }else {//判断下一个字符是数字吗?不是则入栈
                    if (operStack.isOper(str.substring(index+1,index+2).charAt(0))){
                        numStack.push(Integer.parseInt(number));
                        number="";//清空number
                    }
                }
            }

 效果如下

2+5*2-20=-8

上面的计算器实现最麻烦的是,要将人看的懂的表达式转化为机器能看懂的表达式!解决这个问题的办法----波兰表达式。

 

前中后缀表达式规则

前缀即波兰表达式,中缀即人看的表达式,后缀即逆波兰表达式,上面我们实现的计算机属于中缀计算器。其中逆波兰表达式,计算机最懂,所以我以逆波兰为主,制作一个逆波兰计算器

 

逆波兰计算器实现

算法分析:

为了简化代码复杂度,使用系统提供的类集框架代替自己写的链表与栈

将逆波兰表达式分割为一个个单独的字符串数组,通过逆波兰表达式阅读规则:遇到运算符就出栈两个数字,编写计算方法

package cn.dataStructure.demo.stack.calculator;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class 逆波兰计算器 {
    public static void main(String[] args) {
        String str="2 5 * 8 2 / + 1 -";//(2*5)+8/2-1-->2 5 * 8 2 / + 1 -;13
        List<String> list=getListString(str);
        System.out.printf("(2*5)+8/2-1=%d",calculate(list));

    }
    //分割逆波兰表达式入list存储
    public static List<String> getListString(String str){
        String split[]=str.split(" ");
        List <String> list=new ArrayList<>();
        for (String temp:split) {
            list.add(temp);
        }
        return list;
    }
    //逆波兰表达式计算
    public static int calculate(List<String> list){
        Stack<String> stack=new Stack<>();
        for (String temp:list){
            if (temp.matches("\\d+")){//正则匹配多位数字
                stack.push(temp);
            }else {
                int num1= Integer.parseInt(stack.pop());
                int num2= Integer.parseInt(stack.pop());
                int value=0;
                switch (temp){
                    case "+":
                        value=num2+num1;
                        break;
                    case "-":
                        value=num2-num1;
                        break;
                    case "*":
                        value=num2*num1;
                        break;
                    case "/":
                        value=num2/num1;
                        break;
                    default:
                        throw new RuntimeException("运算符有误");
                }
                stack.push(""+value);//利用+将int转为string
            }
        }
        return Integer.parseInt(stack.pop());
    }
}
2*5+8/2-1=13

你有没有一个疑问,有没有可以把中缀表达式自动转化为逆波兰表达式的算法?有,如下:

中缀-->逆波兰

举个例子:“1+((2+3)*4)-5”-->“1 2 3 + 4 * + 5 -”

在整个过程中,s1不断进行入栈,出栈操作,所以s1一定为栈结构,但s2只有入栈,没有出栈,且若s2以栈结构时,最后输出逆波兰表达式时,需要逆向输出。所以我们使用ArrayList来代替栈结构。

在上面的代码中我们已经实现了逆波兰表达式计算,下面实现中缀表达式转逆波兰。算法流程和上面图片中描述的一样

算法分析:

1,对getListString方法进行改造,使之可以对中缀表达式字符串进行分割,该算法支持多位整数。

public static List<String> getListString(String str){
        List<String> list=new ArrayList();
        int index=0;//字符串索引标记
        char ch;//拆分的字符
        String string;//拼接的字符串
        do {
            ch=str.charAt(index);
            if (ch<48||ch>57){//非数字
                list.add(""+ch);
                index++;
            }else {//是数字,考虑多位问题
                string="";
                //index不能越界,且下一位仍然为数字
                while (index<str.length()&&(ch=str.charAt(index))>=48&&(ch=str.charAt(index))<=57){
                    string+=ch;
                    index++;
                }
                list.add(string);
            }
        }while (index<str.length());
        return list;
    }

2,新增parseSuffix方法,用于将拆分好的中缀表达式转化为逆波兰表达式。

这里有个坑:进行字符串比较的时候要用equals方法,不要用==比较。这里牵扯到Java字符串底层机制(入池与不入池),详见我的Java SE博客:【String类-字符串比较】

//中缀表达式转逆波兰表达式的方法
    public static List<String> parseSuffix(List<String> list){
        Stack<String> s1=new Stack<>();//s1用于存储运算符
        List<String > s2=new ArrayList();//s2用于存储结果
        for (String temp:list){
            if (temp.matches("\\d+")){//是数字,入s2栈
                s2.add(temp);
            }else {//非数字(运算符,括号)
                //是括号
                if (temp.equals("(")){//(,直接入栈
                    s1.push(temp);
                }else if (temp.equals(")")){//),不断s1弹出运算符入s2,直到遇到s1的(为至,并将这一对括号丢弃
                    while (!s1.peek().equals("(")){
                        s2.add(s1.pop());
                    }
                    s1.pop();//抛弃括号
                }else if (s1.isEmpty()){//s1空,直接入栈
                    s1.push(temp);
                }else if (s1.peek().equals("(")){//s1的栈顶为(,直接入栈
                    s1.push(temp);
                }else if (getPriority(temp)>getPriority(s1.peek())){//当前运算符优先级>栈顶优先级,直接入栈
                    s1.push(temp);
                }else {
                    while (s1.size()!=0&&getPriority(temp)<=getPriority(s1.peek())){
                        s2.add(s1.pop());
                    }
                    s1.push(temp);
                }
            }
        }
        //将s1剩余字符串依次加入s2
        while (s1.size()!=0){
            s2.add(s1.pop());
        }
        return s2;
    }

3,parseSuffix方法需要getPriority方法(获取操作符优先级)支持

//获取优先级
    public static int getPriority(String oper){
        int value=0;
        switch (oper){
            case "+":
                value=1;
                break;
            case "-":
                value=1;
                break;
            case "*":
                value=2;
                break;
            case "/":
                value=2;
                break;
        }
        return value;
    }

4,在主方法编辑测试代码

String str="1+((2+3)*4)-5";//1 2 3 + 4 * + 5 -
        List<String> list=getListString(str);//拆分字符串
        List<String> suffix=parseSuffix(list);//获取逆波兰表达式
        System.out.println(str+"="+calculate(suffix));
1+((2+3)*4)-5=16

 

【数据结构与算法整理总结目录 :>】<-- 宝藏在此(doge)  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值