作者前言
大家好,我是阿濠,今篇内容跟大家分享的是数据结构之栈,很高兴分享到segmentfault与大家一起学习交流,初次见面请大家多多关照,一起学习进步.
一、什么是栈
首先输入一个表达式:[7*2*2-5+1-5+3-3
]
请问:结果是多少?
请问:计算机底层是如何运算得到结果的?(注意不是简单的把算式列出运算)
因为我们看这个算式7*2*2-5+1-5+3-3
,但是计算机怎么理解这个算式的(对计算机而言,它接收到的就是一个字符串)
这就需要涉及到栈
了
栈的介绍
举个例子:就像叠盘子
一样,后放的盘子总是在上面,拿盘子时是从上面拿,也就是先拿后来放上面的盘子,最后的盘子是最早放的。
1.栈的英文为stack
2.栈是一个先入后出
(FILO-First-In-Last-Out)的有序列表
3.栈是限制线性表中,元素的插入和删除只能在线性表的同一端
进行的一种特殊线性表。允许插入和删除的一端
,为变化的一端
,称为栈顶(Top)
另一端为固定的一端,称为栈底(Bottom)
。
4.根据栈的定义可知,最先放入栈
中元素在栈底
,最后放入
的元素在栈项
,而删除元素刚好相反
,最后放入
的元素最先删除
,最先放入
的元素最后删除
5.出栈(pop)
和入栈(push)
的概念(示意图)
栈的应用场景
1) 子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
2)处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址
外,也将参数
、区域变量等数据存入堆栈
中。
3)表达式的转换[中缀表达式转后缀表达式
]与求值(实际解决)。
4)二叉树
的遍历
。
5)图形的深度优先(depth- first)搜索法
。
栈的插入和删除操作
因为栈的本质是线性表
,线性表有两种存储方式,所以栈也分两种存储方式分别为栈的顺序存储结构
、栈的链式存储结构
栈的插入操作(Push)
叫做进栈,也称为入栈,对于顺序栈的进栈操作只需将新的数据
元素存入栈内
,然后让记录栈内元素个数的变量加1
,程序即可再次通过arr\[size-1\]
重新访问新的栈顶元素
。进栈操作示意图如下:
由于顺序栈底层
通常会采用数组
来保存数据元素,因此可能出现的情况是:当程序试图让一个数据元素进栈时,底层数据已满
,那么就必须扩充
底层数组的长度
来容纳新进栈的数据元素。
栈的删除操作(Pop)
叫做出栈,对于顺序栈的出栈操作而言,需要将栈顶元素弹出栈,程序要做两件事。
- 让记录栈内元素个数的变量减1.
- 释放数组对栈顶元素的引用。
对于删除操作来说,只要让记录栈内元素个数的size减1
,程序即可通过arr\[size-1\]
`访问到
新的栈顶元素。但
不要忘记释放`原来栈顶的数组引用
,否则会引起内存泄漏
。
栈比普通线性表的功能更弱,栈是一种被限制过的线性表,只能从栈顶插入,删除数据元素
。
栈的链式存储结构及实现
可以采用单链表来保存栈
中所有元素,这种链式结构的栈也被称为栈链
。对于栈链而言,栈顶元素不断地改变,程序只要使用一个top
引用来记录当前的栈顶元素
即可。
链式的进栈操作
,只需要做几件件事:
- 让top引用指向新元素添加的元素
- 新元素的next引用指向原来的栈顶元素。
- 让记录栈内元素个数的size变量加1.
链式的出栈操作
,只需要做几件件事:
- 释放原来的栈顶元素
- 让top引用指向原栈顶元素的下一个元素
- 让记录栈内元素个数的size变量减1.
从空间利用率的角度说,链栈的空间利用率比顺序栈的空间利用率要高一些。
二、栈的快速入门
使用数组模拟栈(示意图)
思路分析:
1.使用数组ArrayList模拟栈操作
2.定义Top 指示为栈顶,初始化为-1
3.入栈的操作:当有数据加入到栈时,top++; stack[top]= data;
4.出栈的操作,int value = stack[top]; top--, return valuel
5.栈满:top == maxSize-1
6.栈空:top == -1
使用ArrayStack表示栈 代码如下:
class ArrayStack{
private int maxSize;//栈的大小
private int[] stack;//数组,使用数组模拟栈,数据放入数组
private int top=-1;//top表示栈顶 初始话为-1
public ArrayStack(int maxSize) {
this.maxSize = maxSize;
stack =new int[this.maxSize];
this.top = -1;
}
//栈满
public boolean isFu1l() {
return top == maxSize - 1;
}
//栈空
public boolean isEmpty(){
return top == -1;
}
//入栈
public void push(int value) {
//先判断栈是否满
if(isFu1l()) {
System.out.println("栈满");
return;
}
//栈顶++指向新数据
stack[++top] = value;
}
//出栈 将栈顶数据返回
public int pop() {
//先判断栈是否空
if(isEmpty()) {
//抛出异常
throw new RuntimeException("栈空, 没有数据~");
}
//临时保存栈顶数据
int value = stack[top];
top--;//栈顶--指向原栈顶后一元素
return value;//返回临时保存栈顶的数据
}
//显示栈的情况[遍历栈],遍历时,需要从栈顶开始显示数据
public void list() {
//判断栈是否为空,为空无法遍历
if(isEmpty()) {
System .out.println("栈空,没有数据~~");
return;
}
//需要从栈顶开始显示数据
for(int i=top;i>=0;i--){
System.out .printf("stack[%d]=%d\n", i, stack[i]);
}
}
}
添加数据测试ArrayStack方法操作
//先创建-个ArrayStack对象->表示栈
ArrayStack stack = new ArrayStack(4);
System.out.println("入栈数据:10、4、3、2");
stack.push(10);
stack.push(4);
stack.push(3);
stack.push(2);
System.out.println("当前栈的数据-----------");
stack.list();
System.out.println("栈顶出栈、当前栈的数据-----------");
stack.pop();
stack.list();
运行结果如下:
入栈数据:10、4、3、2
当前栈的数据-----------
stack[3]=2
stack[2]=3
stack[1]=4
stack[0]=10
栈顶出栈、当前栈的数据-----------
stack[2]=3
stack[1]=4
stack[0]=10
栈的经典使用场景浏览器的前进、后退功能
- 使用两个栈X和Y,首次浏览的界面入X栈。
- 后退时,依次从X中出栈,并将出栈的数据依次放入Y栈
- 前进时,从Y栈中出栈,在依次入X栈。
- 当X栈中没有数据时,说明不能后退了;
- 当Y栈中没有数据时,说明不能前进了。
比如你顺序查看了 a,b,c 三个页面,我们就依次把 a,b,c 压入栈
这个时候,两个栈的数据就是这个样子:
当你通过浏览器的后退按钮,从页面 c 后退到页面 a 之后,我们就依次把 c 和 b 从栈 X 中弹出,并且依次放入到栈 Y。
这个时候,两个栈的数据就是这个样子:
这个时候你又想看页面 b,于是你又点击前进按钮回到 b 页面,我们就把 b 再从栈 Y 中出栈,放入栈 X 中。
此时两个栈的数据是这个样子:
这个时候,你通过页面 b 又跳转到新的页面 d 了,页面 c 就无法再通过前进、后退按钮重复查看了,所以需要清空栈 Y。
此时两个栈的数据这个样子:
三、使用栈实现综合计算器(中缀表达式)
使用栈完成表达式计算:7*2*2-5+1-5+3-3
提示:使用两个栈完成计算
思路分析:
1.通过值index
(索引)遍历
表达式字符串
2.若扫描
到是数字
,则直接放入数栈numStack
3.若扫描
到是符号
分以下情况:
➢ 符号栈为空
则直接入栈
➢如果符号栈有操作符
,就进行比较
,如果当前的操作符
的优先级小于或者等于栈中的操作符
,就需要从数栈中pop出两个数
,并从符号栈中pop出一个符号
,进行运算
,将得到结果
并放入数栈
,然后将当前的操作符入符号栈
➢如果当前的操作符
的优先级大于栈中的操作符,就直接入符号栈
4.当表达式扫描完毕
,就顺序的从数栈和符号栈中pop出相应的数和符号
,并运行.
5.最后在数栈
只有一个数字
,就是表达式的结果
问题简单化
一、计算表达式:3+2*6-2
根据思路放入扫描并将数字与符号放入栈中
此时下一扫描到的符号是 *
,它比 +
高,按照思路当前符号优先级高于栈中操作符
,直接入栈
此时下一扫描到的符号是 -
,它比 *
低,按照思路当前符号优先级小于或者等于栈中的操作符
,需要从数栈中pop出两个数
,并从符号栈中pop出一个符号
,进行运算
,将得到结果
并放入数栈
,然后将当前的操作符入符号栈
此时下一位扫到的是数字 2
将它直接放入数栈,此时表达式扫描完毕
,就顺序的从数栈和符号栈中pop出相应的数和符号
我们根据之前数组模拟栈的代码进行修改实现思路图,编码如下:
class ArrayStack2{
private int maxSize;//栈的大小
private int[] stack;//数组,使用数组模拟栈,数据放入数组
private int top=-1;//top表示栈顶 初始话为-1
public ArrayStack2(int maxSize) {
this.maxSize = maxSize;
stack =new int[this.maxSize];
this.top = -1;
}
//直接返回栈顶数据
public int peek(){
return stack[top];
}
//栈满
public boolean isFu1l() {
return top == maxSize - 1;
}
//栈空
public boolean isEmpty(){
return top == -1;
}
//入栈
public void push(int value) {
//先判断栈是否满
if(isFu1l()) {
System.out.println("栈满");
return;
}
//栈顶++指向新数据
stack[++top] = value;
}
//出栈 将栈顶数据返回
public int pop() {
//先判断栈是否空
if(isEmpty()) {
//抛出异常
throw new RuntimeException("栈空, 没有数据~");
}
//临时保存栈顶数据
int value = stack[top];
top--;//栈顶--指向原栈顶后一元素
return value;//返回临时保存栈顶的数据
}
//显示栈的情况[遍历栈],遍历时,需要从栈顶开始显示数据
public void list() {
//判断栈是否为空,为空无法遍历
if(isEmpty()) {
System .out.println("栈空,没有数据~~");
return;
}
//需要从栈顶开始显示数据
for(int i=top;i>=0;i--){
System.out .printf("stack[%d]=%d\n", i, stack[i]);
}
}
}
并可以进行自定义运算符与自定义优先级,编码如下:
//返回运算符的优先级,优先级是程序员来确定,优先级使用数字表示
//数字越大,则优先级就越高.
public int priority(int oper) {
if(oper == '*' || oper == '/'){
return 1;
} else if (oper == '+'| oper == '-') {
return 0;
} else {
return -1; //假定目前的表达式只有+,- ,*,/
}
}
//判断是不是一-个运算符
public boolean is0per(char val) {
return val == '+'|| val == '-' || val == '*' || val == '/';
}
//计算方法
public int cal(int num1, int num2 , int oper) {
int res = 0; //res 用于存放计算的结果
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;
}
进行实验添加测试数据看看效果
//根据前面思路,完成表达式的运算
String expression = "3+2*6-2";
//创建两个栈,数栈,一个符号栈
ArrayStack2 numStack = new ArrayStack2(10);
ArrayStack2 operStack = new ArrayStack2(10);
//定义需要的相关变量
int index = 0;//用于扫描表达式里的每一位字符
int num1 = 0;
int num2 = 0;
int oper = 0;//用于存放运算符级别
int res = 0;//用于存放表达式结果
char ch = ' '; //将每次扫描得到char保存到ch
//开始while循环的扫描expression
while (true) {
//依次得到expression的每一个字符
//比如说获取表达式'3' 是字符串'3' 将它转换成char
ch = expression.substring(index, index + 1).charAt(0);
//判断ch是什么,然后做相应的处理
//如果是运算符
if (operStack.is0per(ch)) {
//断当前的符号栈是否为空 如果符号栈有操作符,就进行比较
if( !operStack.isEmpty()) {
//如果当前的操作符的优先级小于或者等于栈中的操作符,就需要从数栈中pop出两个数,
//在从符号栈中pop出一个符号,进行运算,将得到结果,入数栈,然后将当前的操作符入符号栈
//operStack. priority(ch) 获取运算符的优先级别 0/1
if(operStack. priority(ch) <= operStack. priority(operStack . peek())) {
//从数栈中pop出两个数
num1 = numStack.pop();
num2 = numStack.pop();
//从符号栈中pop出一个符号
oper = operStack. pop();
//进行运算
res = numStack.cal(num1, num2, oper);
//把运算的结果入数栈
numStack. push(res);
//然后将当前的操作符入符号栈
operStack. push(ch);
} else {
//如果当前的操作符的优先级大于栈中的操作符,就直接入符号栈。
operStack. push(ch);
}
}else {
//如果符号栈没有操作符直接入栈
operStack.push(ch);
}
}else{
//如果是数字 放入数栈
numStack.push(ch - 48 );
}
//让index + 1,并判断是否扫描到expression最后.
index++;
if (index >= expression.length()) {
//证明已扫描完毕退出循环结束
break;
}
}
//当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并运行。
while(true) {
//如果符号栈为空,则计算到最后的结果,数栈中只有一个数字[结果]
if(operStack. isEmpty()) {
break;
}
num1 = numStack. pop();
num2 = numStack. pop();
oper = operStack. pop();
//计算最终结果
res = numStack.cal(num1, num2, oper);
numStack.push(res);//将结果放入数栈中
}
System. out. printf("表达式%s = %d", expression, numStack . pop());
有人就会好奇数字放入数栈时push( ch - 48 ),为什么-48?
因为此时的ch = expression.substring( index , index + 1).chatAt(0)
表达式里的1 是'1' 而对应的ASCII码是49
,刚好相差48
那么运行起来的结果如下:
表达式 3 + 2*6 - 2 = 13
那么我们使用中缀表达式计算时还是有些问题的,比如说无法进行表达式:70-2*6+1
问题分析并优化
1.多位数无法进行计算
2.逻辑判断时只要是一个数字就入栈,如果是多位数则无法完整入栈
3.index处理时需要判断当前数字后面是数字还是符号
4.定义变量用于拼接当前数字后面的数字,入栈后清空
5.若index是最后一位则无需操作
//如果是数字 放入数栈
//numStack.push(ch - 48 );
//while循环外声明拼接变量
String keepNum="";
//分析思路
//1.当处理多位数时,不能发现是一个数就立即入栈,因为他可能是多位数
//2.在处理数,需要向expression的表达式的index后再看一位,如果是数就进行扫描,如果是符号才入栈
//3.因此我们需要定义一个变量字符串,用于拼接
//处理多位数
keepNum += ch;
//如果ch已经是expression的最后一位,就直接入栈
if (index == expression.length() - 1) {
numStack.push(Integer .parseInt(keepNum));
}else {
//判断下一个字符是不是数字,如果是数字,就继续扫描,如果是运算符,则入栈
//注意是看后一位,不是index++
if (operStack.is0per(expression.substring(index + 1, index + 2).charAt(0))) {
//如果后一位是运算符,则入栈keepNum = "1"或者"123"
numStack.push(Integer.parseInt(keepNum));
//重要的!!!!!,keepNum清空
keepNum = "";
}
}
表达式 30+ 2*6 - 2 = 40
表达式 7*2*2-5+1-5+3-3 =18
四、前缀、中缀、后缀表达式(逆波兰表达式)
前缀表达式(波兰表达式)
1)前缀表达式又称波兰表达式
,前缀表达式
的运算符
位于操作数之前
2)举例说明: (3+4)* 5 - 6 对应的前缀表达式就是 - x + 3 4 5 6
前缀表达式的计算机求值
➢从右至左
扫描表达式,遇到数字
时,将数字压入堆栈
➢遇到运算符
时,弹出栈顶
的两个数
,用运算符
对它们做相应的计算(栈顶元素和次项元素)
,并将结果入栈
➢重复
上述过程直到表达式最左端
,最后
运算得出的值
即为表达式的结果
比如计算:(3+4)x 5 - 6 对应的表达式: - x + 3 4 5 6
计算机求值步骤:
1.从右至左
扫描,将6、5、4、3压入堆栈
2.遇到+运算符
,因此弹出3和4
(3为栈顶元素,4为次项元素
),计算
出3+4的值
,得7
,再将7入栈
3.接下来是 * 运算符
,因此弹出7和5
, 计算出7 x 5=35
,将35入栈
4.最后是 - 运算符
,计算出35-6
的值,即29
,由此得出最终结果
中缀表达式(常见表达式)
1)中缀表达式就是常见
的运算表达式
,如:(3+4)X5-6
2)中缀表达式的求值
是我们人最熟悉的
,但是对计算机
来说却不好操作
比如说有小括号先计算
,没有按优先级计算
3)因此在计算结果时,往往会将中缀表达式
转成其它表达式
来操作(一般转成后缀表达式
)
后缀表达式(逆波兰表达式)
1)后缀表达式又称逆波兰表达式
,与前缀表达式相似,只是运算符
位于操作数之后
2)中缀表达式举例说明: (3+4) x 5 - 6 对应的后缀表达式就是 34 + 5 x 6 -
后缀表达式的计算机求值
➢从左至右
扫描表达式,遇到数字
时,将数字压入堆栈
➢遇到运算符
时,弹出栈项
的两个数
,用运算符
对它们做相应的计算(次顶元素和栈项元素)
,并将结果入栈
➢重复
上述过程直到表达式最右端
,最后
运算得出的值
即为表达式的结果
例如: (3+4)* 5-6 对应的后缀表达式就是 3 4 + 5 x 6 -
,计算机求值步骤:
1.从左至右
扫描,将3和4压入堆栈
;
2.遇到+运算符
, 因此弹出4和3
(4为栈项元素,3为次顶元素
),计算
出3+4的值
,得7
,再将7入栈、5入栈;
3.接下来是*运算符
,因此弹出5和7
,计算出7x5=35
,将35入栈
、6入栈;
4.最后是 -运算符
,计算出35-6
的值, 即29
, 由此得出最终结果
对比前缀表表达式与后缀表达式,前缀表达式
最后-运算
时35是栈顶
(上边),后缀表达式
时35是栈底
(下边),不同表达式减数与被减数顺序不一样
,需要小心使用
五、使用栈实现综合计算器(后缀表达式)
完成一个逆波兰计算器 要求如下:
1)输入一个逆波兰表达式(后缀表达式),使用栈(Stack),计算其结果
2)支持小括号和多位数整数
(讲的是数据结构,因此计算器进行简化,只支持对整数的计算。)
代码如下:
//先定义给逆波兰表达式
//(3+4)*5-6 => 34 + 5 x 6 -
//说明为了方便,逆波兰表达式的数字和符号使用空格隔开
String suffixExpression ="3 4 + 5 x 6 -";
波兰计算器代码如下:
public class PolandNotation {
//将一个逆波兰表达式,依次将数据和运算符放入到ArrayList中
public static List<String> getListString(String suffixExpression) {
//将suffixExpression分割空格
String[] split = suffixExpression.split(" ");
List<String> list = new ArrayList<String>();
for (String ele : split) {
list.add(ele);
}
return list;
}
//完成对逆波兰表达式的运算
/**
* 1)从左至右扫描,将3和4压入堆栈;
* 2)遇到+运算符,因此弹出4和3 (4为栈顶元素, 3为次顶元素)
* 3)计算出3+4的值,得7,再将7入栈;将5入栈;
* 4)接下来是x运算符,因此弹出5和7,
* 5)计算出7x5=35,将35入栈;将6入栈;
* 6)最后是-运算符,计算出35-6的值,即29,由此得出最终结果
* */
public static int calculate(List<String> is) {
//创建给栈,只需要一个栈即可
Stack<String> stack = new Stack<String>();
//遍历is
for(String item: is) {
//这里使用正则表达式来取出数
//匹配的是多位数
if (item.matches("\\d+")) {
//入栈
stack.push(item);
} else{
//pop出两个数,并运算,再入栈
int num2 = Integer . parseInt(stack.pop());
int num1 = Integer.parseInt(stack. pop());
int res = 0;
if (item.equals("+")) {
res = num1 + num2;
} else if (item.equals("-")) {
res = num1 - num2;
} else if (item. equals("x")) {
res = num1 *
num2;
} else if (item. equals("/")) {
res = num1 / num2;
} else {
throw new RuntimeException( "运算符有误" );
}
//把res入栈
stack.push("" + res);
}
}
//最后栈中留下的是最后的结果 直接返回栈顶数据
return Integer.parseInt(stack.pop());
}
}
运行操作如下:
//1. 先将"34+5x6-"=>放到ArrayList中
//2.将ArrayList传递给一个方法,遍历ArrayList配合栈完成计算
//将字符串放入ArrayList直接遍历ArrayList即可
//以前是扫描字符串,现在直接遍历ArrayList即可
List<String> list = getListString(suffixExpression);
System .out. println("rpnList=" + list);
int res = calculate(list);
System.out.print("结果="+res);
运行结果如下:
rpnList=[3, 4, +, 5, x, 6, -]
结果=29
我们发现后缀表达式更于简单些,那么如何将我们人平常使用的中缀表达式转为后缀表达式呢?
六、将中缀表达式转后缀表达式
具体步骤如下:
➢初始化两个栈
:运算符栈
s1和储存中间结果的栈
s2;
➢从左至右
扫描中缀表达式
;
➢遇
到操作数
时,将其压s2
;
➢遇
到运算符
时,比较
其与s1栈项运算符
的优先级
:
1.如果s1为空
,或栈顶运算符
为左括号“(
", 则直接将此运算符入栈
;
2.否则,若优先级
比栈顶运算符
的高
,也将运算符压入s1
;
3.否则,将s1栈项的运算符弹出
并压入
到s2
中,再次转到第一点
与s1
中新的栈项运算符
相比较
;
➢遇到括号时:
1.如果是左括号
“(
”, 则直接压入s1
2.如果是右括号
“)
”, 则依次弹出s1栈项的运算符
,并压入s2
,直到遇到左括号
为止,此时将这一对括号丢弃
➢重复步骤
,直到表达式
的最右边
➢将s1中剩余的运算符依次弹出并压入s2
➢依次弹出s2
中的元素并输出
,结果
的逆序
即为中缀表达式
对应的后缀表达式
举例说明:
将中缀表达式“1+((2X3)X4)-5”转换为后缀表达式"123+4 X + 5 -
"
按照思路进行示意图解
1.初始化两个栈运算符栈
s1和储存中间结果的栈
s2;
2.从左至右
扫描中缀表达式
;遇
到操作数
时,将其压s2
;遇
到运算符
时,比较
其与s1栈项运算符
的优先级
:
3.遇到括号时:如果是左括号
“(
”, 则直接压入s1
,如果是右括号
“)
”, 则依次弹出s1栈项的运算符
,并压入s2
,直到遇到左括号
为止
4.重复步骤
,直到表达式
的最右边
,此时遇到运算符+
,比较优先级,如果s1为空
,或栈顶运算符
为左括号“(
", 则直接将此运算符入栈
;
5.重复步骤
,直到表达式
的最右边
,此时遇到运算符)
,依次弹出s1栈项的运算符压入s2
,直到遇到左括号
为止,此时将这一对括号丢弃
,相当于要把一对()消除掉
6.重复步骤
,直到表达式
的最右边
,此时遇到运算符*
,比较
其与s1栈项运算符
的优先级
,如果s1为空
,或栈顶运算符
为左括号“(
", 则直接将此运算符入栈
;
7.重复步骤
,直到表达式
的最右边
,此时遇到运算符)
,依次弹出s1栈项的运算符压入s2
,直到遇到左括号
为止,此时将这一对括号丢弃
,相当于要把一对()消除掉
8.重复步骤
,直到表达式
的最右边
,此时遇到运算符-
,此时操作符 + 与操作符 - 一个级别
,满足将s1栈项的运算符弹出
并压入
到s2
中,再次转到
与s1
中新的栈项运算符
相比较
;
9.此时回到如果s1为空
,或栈顶运算符
为左括号“(
", 则直接将此运算符入栈
;
10.重复步骤
,直到表达式
的最右边
,此时遇到运算符5
将其压s2
;
11.将s1中剩余的运算符依次弹出并压入s2
12.依次弹出s2
中的元素并输出
,结果
的逆序
即为中缀表达式
对应的后缀表达式
将S2出栈:- 5 + * 4 + 3 2 1 逆序即为中缀表达式
对应的后缀表达式
所以结果=1 2 3 + 4 * + 5 -
那么我们是怎么知道中缀表达式是使用这个思路呢?
其实这个思路是相对应的设计者与相关的专家告诉我们的,我们进行运用,但是我们能不能自己设计一个思路或算法呢?
举个例子:
有一门武功降龙十八掌秘籍,你是不是可以
1.按照秘籍与心法,进行学习运用
2.创造属于自己的降龙十八掌秘籍
如果是创造属于自己的降龙十八掌那么这个难度是不言而喻的,算法其实也是相对应的道理。
学习算法:
1.理解算法,进行灵活运用解决问题
2.设计算法,解决问题
设计成熟的算法需要很强的数学功底与计算机理解深度,但是运用前人的经验就是我们目前需要做的事情。
代码实现中缀表达式转后缀表达式
方法1:将中缀表达式转成对应的List
即"1+((2+3)x4)-5
"转换成ArrayList[1,+,((,2,+,3,),x,4,),-,5]
;
public static List<String> toInfixExpressionList(String s) {
//定义一个List,存放中缀表达式对应的内容
List<String> ls = new ArrayList<String>();
int i = 0; //这时是一个指针,用于遍历中缀表达式字符串
String str; //对多位数的拼接
char c; //每遍历到一个字符,就放入到c
do {
//如果c是-个非数字,我需要加入到1s
//数字0-9 的ASCII码是 48 - 57
//大于则不是数字,将他放入1s
if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57) {
ls.add("" + c);
i++; //i需要 后移
} else {
//如果是一个数,需要考虑多位数
str = ""; //先将str. 置成""
//'0'[48]->'9'[57] 大于并小于57 是数字
while (i < s.length() && ( c = s.charAt(i)) >= 48 && (c = s.charAt(i)) <= 57) {
str += c;//拼接
i++;
}
ls.add(str);
}
}while (i < s.length());
return ls;
}
//中缀表达式转后缀表达式
//完成将一个中缀表达式转成后缀表达式的功能
//说明
//目标:1+((2+3)x4)-5 =>转成1 2 3 + 4 x + 5 -
//1.因为直接对str进行操作,不方便,因此先将“1+((2+3)x4)-5" =》中缀的表达式对应的List
//即"1+((2+3)x4)-5" => ArrayList [1,+,(,(,2,+,3,),x,4,),-,5]
String str ="1+((2+3)x4)-5";
List<String> list=toInfixExpressionList(str);
System.out.println("中缀表达式转后缀表达式:"+list);
运行结果如下:
中缀表达式转后缀表达式:[1, +, (, (, 2, +, 3, ), x, 4, ), -, 5]
方法2:将ArrayList [1,+,(,(,2,+,3,),x,4,),-,5]
转成1 2 3 + 4 x + 5 -
按照思路开始定义两个栈,但是s2这个栈因为在整个转换过程,没有pop操作,而最后步骤需要逆序输出比较麻烦,所以直接使用ArrayList也可以
//编写一个类Operation可以返回-一个运算符对应的优先级
class Operation {
private static final int ADD = 1;//加
private static final int SUB = 1;//减
private static final int MUL = 2;//乘
private static final int DIV = 2;//除
//写一个方法,返回对应的优先级数字
public static int getValue(String operation) {
int result = 0;
if ("+".equals(operation)) {
result = ADD;
} else if ("-".equals(operation)) {
result = SUB;
} else if ("x".equals(operation)) {
result = MUL;
} else if ("/".equals(operation)) {
result = DIV;
} else {
System.out.println("不存在该运算符");
}
return result;
}
}
中缀表达式转后缀表达式代码如下:
//即ArrayList [1,+,(,(,2,+,3,),x,4,),-,5] => ArrayList [1,2,3,+,4,x,+,5,-]
//方法:将得到的中缀表达式对应的List =>后缀表达式对应的List
public static List<String> parseSuffixExpreesionList(List<String> ls) {
//定义两个栈
Stack<String> s1 = new Stack<String>(); // 符号栈
//说明:因为s2这个栈,在整个转换过程中,没有pop操作,而且后面我们还需要逆序输出
//因此比较麻烦,这里我们就不用Stack<String>直接使用List<String> s2
//Stack<String> s2 = new Stack<String>(); //储存中间结果的栈s2
List<String> s2 = new ArrayList<String>(); //储存中间结果的Lists2
//遍历1s
for (String item : ls) {
// 按照思路分析
// 如果是一个数,加入s2
if (item.matches("\\d+")) {
s2.add(item);
} else if (item.equals("(")) {
//遇到括号时:如果是左括号“(”, 则直接压入s1
s1.push(item);
} else if (item.equals(")")) {
//如果是右括号“)”
//直到遇到左括号为止,此时将这一对括号丢弃
while (!s1.peek().equals("(")) {
//依次弹出s1栈项的运算符,并压入s2
s2.add(s1.pop());
}
//将小括号 ( 弹出 此时将这一对括号丢弃
s1.pop();
} else {
//遇到运算符时,比较其与s1栈项运算符的优先级:
//1.如果s1为空,或栈顶运算符为左括号“(", 则直接将此运算符入栈;
//2.否则,若优先级比栈顶运算符的高,也将运算符压入s1;
//3.否则,将s1栈项的运算符弹出并压入到s2中,再次转到第一点与s1中新的栈项运算符相比较;
while( s1.size()!=0 && !s1.peek().equals("(") && Operation.getValue(s1.peek())>=Operation.getValue(item)){
//将s1栈项的运算符弹出并压入到s2中
s2.add(s1.pop());
}
s1.push(item);
}
}
//将S1中剩余得运算符依次弹出降入S2
while(s1.size()!=0){
s2.add(s1.pop());
}
return s2;//注意因为是存放ArrayList中,正常输出则是逆波兰表达式
}
我们加上使用后缀表达式计算器的方法测试一下数据
String str ="1+((2+3)x4)-5";
List<String> infixExpressionList=toInfixExpressionList(str);
System.out.println("中缀表达式对应的List:"+infixExpressionList);
List<String> suffixExpreesionList=parseSuffixExpreesionList(infixExpressionList);
System.out.println("后缀表达式对应的List:"+suffixExpreesionList);
System.out.printf("expression=%d",calculate(suffixExpreesionList));
运算结果如下:
中缀表达式对应的List:[1, +, (, (, 2, +, 3, ), x, 4, ), -, 5]
后缀表达式对应的List:[1, 2, 3, +, 4, x, +, 5, -]
expression=16
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。