小妹使用栈实现计算器(上)

hello,大家好,今天给大家讲的是利用栈实现一个计算器,思路很简单,大家仔细思考。

好了,废话不多说,马上开始。

小妹:小哥哥,我前几天写了一个计算器,你来帮我看看怎么样吧。

我:恩?计算器,拿来吧你!

public class Cal {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int i = sc.nextInt();
        int i1 = sc.nextInt();
        String s = sc.next();
        if (s.equals("+")) {
            System.out.println(i + i1);
        } else if (s.equals("-")) {
            System.out.println(i - i1);
        } else if (s.equals("*")) {
            System.out.println(i * i1);
        } else if (s.equals("/")) {
            System.out.println(i / i1);
        } 
    }
}

我:再见!

小妹:别呀别呀!这不就是计算器吗?

我:额。。。是。。。是的。

小妹:嘿嘿,我写的不错吧!

我:不错不错。那你知道栈吗?能尝试用这个实现计算器吗?

小妹:唔...我不知道诶,栈是啥?

我:你求我,我给你解释一下。

小妹:啥也不是,再见!

我:别,给你讲不就是了么,咋还生气了呢?

小妹:那你讲,我听不懂了,天灵盖给你掀了。

 

我:好,讲。

1.栈是什么?

我:小妹,把你水杯给我。

小妹:干啥?

我:拿来吧你!

小妹:你....

我:水杯只有一个口,无论是你喝水,还是接水,都要经过这个上边这个口才行,你知道的。而栈这种结构与水杯一样,也是只有一个出口,一个入口,无论你放数据,还是取数据,都需要经过这个出口(入口)才行。

小妹:额,懂是懂了,但是这种结构有什么用呢?没感觉到特殊之处。

我:没感觉到很正常,栈这种结构很有用的,给你举个例子吧:

假设你有一个字符串,我现在让你逆向输出这个字符串,不能改变这个字符串

小妹:用reverse方法呀!

我:额,如果没有这个方法,你咋办?

小妹:嘿嘿,我想想....把字符串存入一个新的数组,然后从高位输出这个字符串,就可以了。

我:哎呀,有料!

小妹:有什么料?神经病犯了?

我:继续继续,你把栈想象成下边这样

你把数据往里放,是一个压着一个的,这个操作叫做入栈,你把数据往外拿,这个操作叫出栈。

好了,讲到这里,那你想想这种结构有什么特点吧。

......(五分钟后)

小妹:既然只有一个入口,那先进来的肯定是最后出去的吧。删除里边的元素也只能先删除上边的,才能删除下边的。

我:这就对了嘛,这就是栈,理解了吧。

小妹:理解是理解了,但是有什么用呢?

我:恩?

小妹:呀!我知道了,刚刚的字符串,我可以使用栈,先放栈中,然后再一个个拿出来,这样就是逆向输出了,哈哈,我真是个天才。

我:对呀,这样不就对了么,api只是给你提供的一个工具,你要自己去思考应该如何实现,不能只能会调用api,有句古话说得好啊,自己动手,丰衣足食。

小妹:似的似的,小哥哥说的是呢。

我:好了,栈你也明白了,那想办法实现一下吧!

......(二十分钟后)

小妹:我写好了,小哥哥过目(我相信正在看文章的你也已经写好了,加油!)

public class Stack {

    private int i = -1;

    private int size;

    private Object[] o;

    public Stack() {
        o = new Object[10];
    }

    public Stack(int size) {
//        init array stack
        o = new Object[size];
        this.size = size;
    }

//    array append
    private void appendArray() {
//        old array length
        int length = o.length;

        Object[] o1 = new Object[length*2];
        for (int i = 0; i < length; i++) {
            o1[i] = o[i];
        }
        for (int i = length; i < o1.length; i++) {
            o1[i] = 0;
        }
    }

    /**
     * stack is empty;
     */
    public boolean isEmpty() {
        return i == -1;
    }

    public boolean isFull() {
        return i == size - 1;
    }

    public void push(Object element) {
        if (isFull()) {
            System.out.println("stack is full");
            return;
        }
        i++;
        this.o[i] = element;
        System.out.println("add success");
    }

    public Object pop() {
        if (isEmpty()) {
            throw new RuntimeException("stack is empty");
        }
        System.out.println(this.o[i] + ": has been pop");
        i--;
        return this.o[i+1];
    }

    public void show() {
        if (isEmpty()) {
            System.out.println("stack is empty");
            return;
        }
        for (int j = 0; j < size; j++) {
            System.out.println("o["+ j + "]" + ":" + o[j]);
        }
    }

    public Object peek() {
        if (isEmpty()) return null;
        else return this.o[i];
    }

    public int getSize() {
        return this.size;
    }

    public int getElementSum() {
        if (isEmpty()) return 0;
        return this.i;
    }

    public int getLength() {
        return this.i;
    }
}

我:写的不错,等你学完泛型,还能进一步优化这个类的,不过现在已经很不错了,今晚给你加个鸡蛋。

小妹:好哇好哇!

我:好了,栈也学完了,下面想想怎么优化你的计算器吧。

小妹:加两个鸡蛋。

我:好,想出来加两个鸡蛋。

....(20分钟后)

小妹:小哥哥,我实在是想不出来了。

我:想不出来?那我就给你讲讲吧。支棱起来听好了。

先给你讲不带括号的吧,这个稍微简单一些,等下再给你说带括号的咋搞!

2.式子分割

式子:

1+2+3

我:你来看这个简单的式子,假定它是一个String的字符串,我们可以怎么拆分它呢?用什么方法?你知道吗小妹。

小妹:这个我知道,你看这样就可以了

public static void split(String s) {
        
    for (int i = 0; i < s.length(); i++) {
        char c = s.charAt(i);
        System.out.println(c);
    }
}

我:对,这样是能分割开,那我再给你变换一下,加入式子改成这个呢?

11+2+3

小妹:还是那样分割啊!哎不对,那样就把11分割开了,这样不行,让我想想(陷入深思)

...(15分钟后)

我:想到了吗?

小妹:有点思路,小哥哥你听听对不对哈;

在扫描字符串的过程中,不扫描到运算符,就一定是数字,那么,我将扫描的数字拼接起来,再转换成一个整数,这样不就可以了么,每次遇见运算符,我就做一次拼接完成了一个数字,这样就解决了多位数的问题了呢!

我:聪明啊你小妹,果然,还得是我教得好啊,哈哈哈哈

小妹:戚,你拉倒吧。我写写代码你看看嗷。

(经过10分钟的艰苦奋斗,终于,代码完成了)

public static void split(String s) {
        String str = "";
        for (int i = 0; i < s.length(); i++) {
            if (Character.isDigit(s.charAt(i))){
                char c = s.charAt(i);
                str += c;
            } else {
                System.out.println(str);
                str = "";
                System.out.println(s.charAt(i));
            }
        }
        if (str != "") {
            System.out.println(str);
        }
}

这样就能拆分好啦,哈哈哈(小妹手舞足蹈的说了起来)

我:不错不错,进步很快,值得表扬。

小妹:说好的讲计算器,你让我写这干嘛?

我:你智商到底是行还是不行啊,不拆分你计算什么?脑子呢!

小妹:你....算了,讲吧,先不k你。

我:啊。。。好吧;重点来了,你可得听好了。在做运算的时候,是有符号的先后顺序的,这个不用多讲吧,我们就先假定高优先级优先(优先级高的数字大)吧。

1+2+3

假设 "+"、"-" 的优先级为1,那么 " * " " / " 的优先级为2

下面再给你补充一些知识。

3.表达式的转换

你知道前缀表达式,中缀表达式,和后缀表达式吗?

小妹:不知道啊,那是什么?

我:我们人类思维最容易接受的是中缀表达式,就像我上面给你说的那个例子。

1+2+3

而计算器与人类不同,计算器要是拥有人类的思维,该多可怕啊!计算器最容易识别的表达式是后缀表达式,而后缀表达式是什么,下面我给你讲解一下你就懂了,你也学过数据结构了,一看就会明白的。

小妹:学过跟会是两码事好吧。

我:额,放心,绝对是一看就懂。

首先,这是个中缀表达式,我们先构造一棵树。

这样的:

这就是中缀表达式,以树的中序遍历得到的结果是 1+2+3,而以后序遍历得到的是 1 2 + 3 +, 这就是后缀表达式(逆波兰表达式)。

补充(树的遍历):

前序:根,左,右

中序:左,根,右

后序:左,右,根

是不是很好理解!

小妹:原来是这啊,我还以为啥呢,不就是一个后续遍历吗。

我:我就说嘛,很好理解的,你还不相信我。那好,现在后缀表达式也给你讲了,那你去实现一下。

小妹:没问题,小意思。

(半个点过去了。。。)

小妹:我没想到怎么写啊,完全没思路,我是不是废了?

我:没想起来才正常,你要是真想到了,那我就没办法教你了,你就是大佬,你是我哥哥。

小妹:噗~真的吗?

我:还能骗你么,好了,给你讲讲思路,你自己去实现代码。

思路分析:
     1.初始化两个栈;运算符栈s1和存储中间结果的栈s2
     2.从左至右扫描中缀表达式
     3.遇到操作符时,将其压入s2
     4.遇到运算符时,比较其与s1栈顶运算符的优先级
        1.如果s1为空,或栈顶运算符为左括号,则直接将此运算符入栈
        2.否则,若优先级比栈顶运算符的高,也将运算符压入s1;
        3.否则,将s1栈顶的运算符弹出并压入s2中,再次转到(4-1)与s1中新的栈顶运算符相比较
     5.遇到括号:
        1.如果是左括号,直接压入s1
        2.如果是右括号,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,
          此时将这一对括号丢弃
     6.重复步骤2-5,直到表达式的最右边
     7.将s1中剩余的运算符依次弹出并压入s2
     8.依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式

仔细思考一下,想想怎么实现哈!呀,中午了,哈哈,我先去吃饭了!

小妹:别啊,我也饿啊,还要加两个鸡蛋呢不是,你骗我?

我:我可没骗你,先写这个转换,写完你再吃吧,哈哈哈,拜拜了您那。

(大家自己先思考,怎么实现,尝试一下实现,马上更新下一篇,点开我的主页查看更新....)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值