栈
概念
栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
通俗来讲,大家可以把栈理解为小时候玩手枪上子弹的玩具,先上进去的子弹是在最下面的,第一个射出的子弹是最后一个上进去的子弹,这就是栈与队列不同的地方:先进后出
思路分析
代码实现
public class ArrayStackDemo {
public static void main(String[] args) {
ArrayStack stack = new ArrayStack(3);
char key = ' ';
Scanner scanner = new Scanner(System.in);
boolean loop = true;
while (loop) {
System.out.println("s(show),显示栈");
System.out.println("a(add),添加数据到栈");
System.out.println("g(get),从栈取数据");
System.out.println("e(exit),退出");
System.out.println("请输入方法:");
key = scanner.next().charAt(0);
switch (key){
case 's' :
try {
stack.show();
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 'a' :
System.out.println("请输入一个数:");
stack.push(scanner.nextInt());
break;
case 'g' :
try {
System.out.println("取出数据:" + stack.pop());
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 'e' :
scanner.close();
loop = false;
break;
}
}
System.out.println("程序结束~~");
}
}
class ArrayStack{
private int maxSize;
private int top;
private int[] array;
public ArrayStack(int maxSize) {
this.maxSize = maxSize;
this.top = -1;
array = new int[this.maxSize];
}
//栈满
public boolean isFull(){
return maxSize - 1 == top;
}
//栈空
public boolean isEmpty(){
return top == -1;
}
//入栈
public void push(int data){
if (isFull()){
System.out.println("栈满,无法添加");
return;
}
top++;
array[top] = data;
}
//出栈
public int pop(){
if (isEmpty()){
throw new RuntimeException("栈空,无法读取");
}
int value = array[top];
top--;
return value;
}
//显示栈
public void show(){
if (isEmpty()){
throw new RuntimeException("栈空");
}
for (int i = top; i >= 0 ; i--) {
System.out.printf("array[%d]=%d\n",i,array[i]);
}
}
}
简易的综合计算器
需求
输入一个字符串(expression),类似于2+3*8-6
,计算出结果,这里的计算器只有+、-、*、/
四种。
思路分析
-
创建两个栈,一个数字栈用于存放数字,一个符号栈用于存放运算符
-
创建一个指针,从头开始遍历expression,指到指针指向expression的末尾
-
如果遍历到的是数字
-
如果该数字是最后一位,直接入栈
-
如果该数字不是最后一位(这里用于计算多位数,否则只能计算个位数)
-
如果该数字的下一位是运算符,直接入栈
-
如果该数字的下一位是数字,记录到临时变量
-
-
-
如果遍历到的是运算符
- 如果符号栈为空,直接入栈
- 如果符号栈不为空,判断优先级
- 如果指针指到的运算符优先级小于等于栈顶的运算符,取出数字栈中的两个数字,取出运算符栈中的一个运算符进行运算,再将运算结果存入数字栈,再将指针指到的运算符存入符号栈
- 如果指针指到的运算符优先级大于栈顶的运算符,直接入栈
-
遍历两个栈,取出数字栈中的两个数字,取出运算符栈中的一个运算符进行运算,再将运算结果存入数字栈。遍历结束条件为符号栈为空,最后留在数字栈中的数据就是运算结果
代码实现
首先我们需要先准备一些工具类,比如判断字符是否为符号,判断符号优先级,计算结果,为了方便,统一追加在ArrayStack中
//判断优先级
public static int priority(int oper){
if (oper == '*' || oper == '/'){
return 1;
}else if (oper == '+' || oper == '-'){
return 0;
}else {
//暂时只考虑加减乘除
return -1;
}
}
//判断是否为运算符
public static boolean isOper(int oper){
return oper == '*' || oper == '/' || oper == '+' || oper == '-';
}
/**
* 计算结果
* @param num1 从栈中取出的第一个数
* @param num2 从栈中取出的第二个数
* @param oper 运算符
* @return
*/
public static int cal(int num1,int num2,int oper){
//用于存放运算结果
int result = 0;
switch (oper){
case '*' :
result = num1 * num2;
break;
case '/' :
result = num2 / num1;
break;
case '+' :
result = num1 + num2;
break;
case '-' :
result = num2 - num1;
break;
}
return result;
}
接下来编写逻辑代码
public class Calculator {
public static void main(String[] args) {
String expression = "200+3*7-6";
//指针,用于遍历表达式
int index = 0;
char val = ' ';
String temp = "";
ArrayStack numStack = new ArrayStack(10);
ArrayStack operStack = new ArrayStack(10);
int num1 = 0;
int num2 = 0;
int oper = 0;
//遍历表达式,将数据存入栈
while (true){
val = expression.substring(index, index + 1).charAt(0);
//如果遍历到的是运算符
if (ArrayStack.isOper(val)){
//如果符号栈为空,直接入栈
if (operStack.isEmpty()){
operStack.push(val);
}else {
//如果指针指到的运算符优先级小于等于栈顶的运算符,取出数字栈中的两个数字,取出运算符栈中的一个运算符进行运算,再将运算结果存入数字栈,再将指针指到的运算符存入符号栈
if (ArrayStack.priority(val) <= ArrayStack.priority(operStack.showTop())) {
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
//计算结果
int result = ArrayStack.cal(num1, num2, oper);
//将结果存入数字栈,将运算符存入符号栈
numStack.push(result);
operStack.push(val);
} else {
//如果指针指到的运算符优先级大于栈顶的运算符,直接入栈
operStack.push(val);
}
}
}else {
temp = temp + val;
//如果已经遍历到最后一位,直接入栈
if (index == expression.length() - 1){
numStack.push(Integer.parseInt(temp));
}else {
//如果遍历到的是数字,并且数字的下一位是运算符,直接入栈
if (ArrayStack.isOper(expression.substring(index + 1, index + 2).charAt(0))) {
numStack.push(Integer.parseInt(temp));
temp = "";
}
}
}
index++;
//如果指针指向表达式末尾,循环结束
if (index == expression.length()){
break;
}
}
//计算栈中数据的结果
while (true){
//如果符号栈中无数据,计算结束
if (operStack.isEmpty()){
break;
}
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
//计算结果
int result = ArrayStack.cal(num1, num2, oper);
numStack.push(result);
}
System.out.printf("表达式%s=%d",expression,numStack.pop());
}
}
前缀、中缀、后缀表达式
中缀表达式
中缀表达式是人最能理解的表达式,例如1+2*3-4
就是中缀表达式,完全按照生活中的数学逻辑编写,但中缀表达式对于计算机来说,反而是一种非常难理解的表达式,上面简易的综合计算器就是中缀表达式所编写的,我们发现过程非常的麻烦
前缀表达式
前缀表达式是一种没有括号的算术表达式,与中缀表达式不同的是,其将运算符写在前面,操作数写在后面。为纪念其发明者波兰数学家Jan Lukasiewicz,前缀表达式也称为“波兰表达式”。例如,- 1 + 2 3
,它等价于1-(2+3)
。
计算逻辑
- 从右往左遍历表达式
- 当遍历到的字符为数字时,直接压入栈
- 当遍历到的字符为符号时,从栈中取出2个数字进行运算,运算结果重新压入栈
- 最后留在栈中的就是表达式的结果
后缀表达式
后缀表达式又被称为“逆波兰表达式”,与前缀表达式完全相反,它是将操作数写在前面,运算符写在后面,不仅便于计算机的理解,对人来说也比前缀表达式理解的要容易一点。例如,1 2 3 + -
,它等价于1-(2+3)
。
计算逻辑
- 从左往右遍历
- 当遍历到的字符为数字时,直接压入栈
- 当遍历到的字符为符号时,从栈中取出2个数字进行运算,运算结果重新压入栈
- 最后留在栈中的就是表达式的结果
逆波兰计算器
逆波兰计算器:输入逆波兰表达式,得到结果,计算逻辑咱们已经在上面分析过了,直接上代码
public class BolandCalculator {
public static void main(String[] args) {
//逆波兰表达式 (3+4)*5-6
String expression = "3 4 + 5 * 6 -";
/*
思路:
将字符串分割,存入arraylist
遍历arraylist计算结果
*/
List<String> charList = string2List(expression);
int result = calculator(charList);
System.out.println(result);
}
/**
* 逆波兰表达式计算器
* @param list
* @return
*/
public static int calculator(List<String> list){
Stack<String> stack = new Stack<>();
for (int i = 0; i < list.size(); i++) {
String str = list.get(i);
if (str.charAt(0) >= 48 && str.charAt(0) <= 57){
//当字符为数字时
stack.push(str);
}else {
//当字符为符号时
int num2 = Integer.parseInt(stack.pop());
int num1 = Integer.parseInt(stack.pop());
int result = 0;
if (str.equals("+")){
result = num1 + num2;
}else if (str.equals("-")){
result = num1 - num2;
}else if (str.equals("*")){
result = num1 * num2;
}else if (str.equals("/")){
result = num1 / num2;
}else {
System.out.println("运算符有误");
}
stack.push("" + result);
}
}
return Integer.parseInt(stack.pop());
}
/**
* string转list<string>
* @param expression 每个字符用空格分开
* @return
*/
public static List<String> string2List(String expression){
String[] split = expression.split(" ");
List<String> charList = new ArrayList<>();
for (String c : split){
charList.add(c);
}
return charList;
}
}
中缀表达式转后缀表达式
后缀表达式计算逻辑已经写完了,并且得到了正确的结果,这并不困难。但是对于人来说,写一个后缀表达式是非常难的,所以我们需要写一个方法来将输入的中缀表达式转换成后缀表达式
思路分析
- 准备两个栈,s1用来存放符号,s2用来存放表达式
- 从左往右遍历中缀表达式
- 当遍历的字符为数字时,直接入s2栈
- 当遍历的字符为运算符时
- s1为空或者s1栈顶运算符为
(
时,直接入s1栈 - 当运算符优先级高于s1栈顶运算符的优先级时,直接入s1栈
- 当运算符优先级不高于s1栈顶运算符的优先级时,取出s1栈顶运算符存入s2栈,再次判断4过程,直到运算符优先级高于s1栈顶运算符的优先级,停止弹出运算符,并且将该运算符压入s1栈
- s1为空或者s1栈顶运算符为
- 当遍历的字符为括号时
- 当字符为
(
时,直接入s1栈 - 当字符为
)
时,取出s1栈顶运算符存入s2栈,重复次过程直到s1栈顶运算符为(
时结束,并且丢弃s1栈顶的(
- 当字符为
- 重复遍历,直到中缀表达式结束
- 遍历取出s1栈的运算符存入s2栈
- 取出s2栈中的数据,倒过来就是后缀表达式
代码实现
在上述分析中,我们用了两个栈,其实只需要用到s1栈,因为s2栈只用来存数据,在过程中没有取出数据,那么可以使用arraylist代替,这样可以不用进行逆序处理。
public class BolandCalculator {
public static void main(String[] args) {
String expression = "8+((4+5)*(4-2))-3";
//1.将字符串转化成arraylist,[(,3,+,4,),*,5,-,6]
List<String> infixArraylist = infixArraylist(expression);
System.out.println("中缀表达式为:" + infixArraylist); //中缀表达式为:[(, 3, +, 4, ), *, 5, -, 6]
//2.中缀表达式转后缀表达式
List<String> suffixArraylist = infix2suffix(infixArraylist);
System.out.println("后缀表达式为:" + suffixArraylist); //后缀表达式为:[3, 4, +, 5, *, 6, -]
int result = calculator(suffixArraylist);
System.out.printf("表达式%s=%d",expression,result);
}
/**
* 中缀表达式转后缀表达式
* @param infix
* @return
*/
public static List<String> infix2suffix(List<String> infix){
//用于存放符号的栈
Stack<String> stack = new Stack<>();
//用于接受表达式的list
ArrayList<String> list = new ArrayList<>();
for (String str : infix){
if (str.charAt(0) >= 48 && str.charAt(0) <=57){
//当字符为数字时,直接加入list
list.add(str);
}else if (stack.size() == 0 || "(".equals(stack.peek()) || "(".equals(str)){
//当栈空或者栈顶为左括号或者字符为左括号时,直接入栈
stack.push(str);
}else if (")".equals(str)){
//当字符为右括号时,将栈中的运算符遍历加入到list中,直到遇见左括号
while (!"(".equals(stack.peek())){
list.add(stack.pop());
}
stack.pop();
}else {
//当字符为符号时,判断优先级
while (stack.size() != 0 && Operator.priority(stack.peek()) >= Operator.priority(str)){
list.add(stack.pop());
}
stack.push(str);
}
}
//将栈中遗留的数据存入list
while (stack.size() > 0){
list.add(stack.pop());
}
return list;
}
/**
* 将中缀表达式转化成中缀表达式的一个list形式
* @param expression
* @return
*/
public static List<String> infixArraylist(String expression){
int index = 0;
ArrayList<String> list = new ArrayList<>();
while (true){
String substring = expression.substring(index, index + 1);
list.add(substring);
index++;
if (index == expression.length()){
break;
}
}
return list;
}
/**
* 逆波兰表达式计算器
* @param list
* @return
*/
public static int calculator(List<String> list){
Stack<String> stack = new Stack<>();
for (int i = 0; i < list.size(); i++) {
String str = list.get(i);
if (str.charAt(0) >= 48 && str.charAt(0) <= 57){
//当字符为数字时
stack.push(str);
}else {
//当字符为符号时
int num2 = Integer.parseInt(stack.pop());
int num1 = Integer.parseInt(stack.pop());
int result = 0;
if (str.equals("+")){
result = num1 + num2;
}else if (str.equals("-")){
result = num1 - num2;
}else if (str.equals("*")){
result = num1 * num2;
}else if (str.equals("/")){
result = num1 / num2;
}else {
System.out.println("运算符有误");
}
stack.push("" + result);
}
}
return Integer.parseInt(stack.pop());
}
}
class Operator{
private static int ADD = 1;
private static int SUB = 1;
private static int MUL = 2;
private static int DIV = 2;
public static int priority(String oper){
int result = 0;
switch (oper){
case "+" :
result = ADD;
break;
case "-" :
result = SUB;
break;
case "*" :
result = MUL;
break;
case "/" :
result = DIV;
break;
}
return result;
}
}