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中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式
仔细思考一下,想想怎么实现哈!呀,中午了,哈哈,我先去吃饭了!
小妹:别啊,我也饿啊,还要加两个鸡蛋呢不是,你骗我?
我:我可没骗你,先写这个转换,写完你再吃吧,哈哈哈,拜拜了您那。
(大家自己先思考,怎么实现,尝试一下实现,马上更新下一篇,点开我的主页查看更新....)