栈的应用[韩顺平算法]
栈的应用
栈的思路分析和代码实现
实现栈的思路:
- 创建数组作为栈用来存储,栈顶top指针初始化为-1
- 空栈的时候栈顶指针就top=-1,当有数据入栈就栈顶指针top++,然后把数据存储到stack[top] = data;
- 出栈的时候先把栈顶的元素拿到 int value = stack[top],然后把栈顶的指针下移, top–, return value
代码实例
public class ArrayStackDemo {
public static void main(String[] args) {
//测试一下ArrayStack是否正确
//先创建个ArrayStack对象->表示栈
ArrayStack stack = new ArrayStack(4);
String key = "";
boolean loop = true; //控制是否退出菜单
Scanner scanner = new Scanner(System.in);
while (loop) {
System.out.println("show:表示显示栈");
System.out.println("exit:退出程序");
System.out.println("push:表示添加数据到栈(入栈)");
System.out.println("pop:表示从栈取出数据(出栈)");
System.out.println("请输入你的选择");
key = scanner.next();
switch (key) {
case "show":
stack.list();
break;
case "push":
System.out.println("请输入一个数");
int value = scanner.nextInt();
stack.push(value);
break;
case "pop":
try {
int res = stack.pop();
System.out.println("出栈的数据" + res);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case "exit":
scanner.close();
loop = false;
break;
default:
break;
}
}
System.out.println("程序运行结束");
}
}
/*
* 实现栈的思路:
* 1. 创建数组作为栈用来存储,栈顶top指针初始化为-1
* 2. 空栈的时候栈顶指针就top=-1,当有数据入栈就栈顶指针top++,然后把数据存储到stack[top] = data;
* 3. 出栈的时候先把栈顶的元素拿到 int value = stack[top],然后把栈顶的指针下移, top--, return value
*
* */
//1.自定义栈
class ArrayStack {
//数组模拟栈
private int[] stack;
//栈最大值
private int maxSize;
//栈顶指针,初始化为-1
private int top = -1;
//2.构造器
public ArrayStack(int maxSize) {
this.maxSize = maxSize;
//获取到栈的元素的个数
//创建栈,使用数组模拟的栈,就是创建一个数组
stack = new int[maxSize];
}
//判断栈满
//就是栈顶到了数组的最后一个下标 即 maxSize-1
public boolean isFull() {
return top == maxSize - 1;
}
//判断栈空
//就是栈顶指针等于-1
public boolean isEmpty() {
return top == -1;
}
//入栈
//参数就是入栈的值
public void push(int value) {
//判断条件了没有?
//首先判断栈是否满
if (isFull()) {
System.out.println("栈已经满了,请滚蛋!");
return;
}
//获取到入栈的值 ,就先栈顶指针后移一位
//然后把value加入到该栈顶指针指向的数组下标的位置中
top++;
stack[top] = value;
}
//出栈
//要求就返回一个值就是出栈的值
public int pop() {
//出栈前也要判断是否空,空就抛出异常
if (isEmpty()) {
//就是这个方法是需要返回值的,所以不能直接通过return;去终止方法
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.print(stack[i] + ",");
System.out.println();
}
}
}
栈实现表达式计算器
中缀表达式思路:
1.两个栈一个数栈一个符号栈
2.栈的功能除了出入栈和遍历还要可以判断运算符的优先级
3.通过index索引对字符串进行遍历,如果数字就进数字栈,如果是符号就分两种情况
3.1如果符号栈是空就直接把符号入栈
3.2如果符号栈里面有符号,就进行优先级比较,如果当前的运算符优先级小于等于栈中的优先级就把栈中的符号出栈并且
数字栈中的两个数字进行运算,得到的结果入数栈,如果当前的运算优先级大于栈中的符号就把符号入栈.
4.当所有表达式都遍历完,就从栈中pop出相应的数和符号,依次进行运算即可.
5.得到最后栈中的数就是表达式的结果
package com.Yjm.stack;
public class Calculator {
/*
* 思路
* 1.两个栈一个数栈一个符号栈
* 2.栈的功能除了出入栈和遍历还要可以判断运算符的优先级
* 3.通过index索引对字符串进行遍历,如果数字就进数字栈,如果是符号就分两种情况
* 3.1如果符号栈是空就直接把符号入栈
* 3.2如果符号栈里面有符号,就进行优先级比较,如果当前的运算符优先级小于等于栈中的优先级就把栈中的符号出栈并且
* 数字栈中的两个数字进行运算,得到的结果入数栈,如果当前的运算优先级大于栈中的符号就把符号入栈.
* 4.当所有表达式都遍历完,就从栈中pop出相应的数和符号,依次进行运算即可.
* 5.得到最后栈中的数就是表达式的结果
* */
public static void main(String[] args) {
//中缀表达式实现表达式计算
//定义表达式
String expression = "30+20*60-2";
//创建一个数栈,一个符号栈
ArrayStack2 numStack = new ArrayStack2(10);
ArrayStack2 operStack = new ArrayStack2(10);
//2.定义需要的相关变量
int index = 0;
int num1 = 0;
int num2 = 0;
int oper = 0;
int res = 0;
char ch = ' ';//将每次扫描得到的char保存到ch
String keepNum = "";//用于拼接多位数
//3.开始while循环的扫描experssion
while (true) {
//因为是循环,所以可以一次得到exprssion的每一个字符
//substring() 方法返回字符串的子字符串
//参数1 -- 起始索引(包括), 索引从 0 开始。
//参数2 -- 结束索引(不包括)。
//charAt(0) 方法 字符串转换成字符
ch = expression.substring(index, index + 1).charAt(0);
//然后判断ch是符号还是数字
//4.判断运算符
//调用运算符判断方法进行判断,如果是运算符
if (operStack.isOper(ch)) {
//就先判断符号栈是否为空
//当符号栈为空就进行入栈
//不为空就再进行下一步判断比较优先级
if (!operStack.isEmpty()) {
//得到当前扫描的运算符和栈顶中的运算符比较优先级
if (operStack.priority(ch) <= operStack.priority(operStack.peek())) {
//如果满足当前的运算符优先级小于等于栈中的优先级
//就分别把数栈的数据和符号栈符号出栈
//先出来的就是后进去的
num1 = numStack.pop();
num2 = numStack.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 {//5.如果是数,就直接入栈
//细节:得到结果是数字在ascii对应的字符集码
//numStack.push(ch - '0');
//多位数的入栈思路
//1.当进来的是一个数字之后就不马上入栈,先把它存储到keepNum字符串中
//2.再判断后一位是不是数字
//字符串拼接
keepNum += ch;
//要先进行数组越界判断,当到最后一位就不用进行后一位查看,一般到最后一位的话也就是数字了可以直接入栈
if (expression.length() - 1 == index) {
//把数字入栈即可
numStack.push(Integer.parseInt(keepNum));
} else {
//查看下一位判断是否是数字
//如果是的话就不进行拼接了,该判断成立则把keepNum字符串入栈到数栈中
if (numStack.isOper(expression.substring(index + 1, index + 2).charAt(0))) {
//字符串转换成数字后入栈
numStack.push(Integer.parseInt(keepNum));
//入栈之后字符串要清空,不然就会一直拼接!!!
keepNum = "";
}
}
}
//让index 索引递增
index++;
if (index > expression.length() - 1) {
break;
}
}
//当表达式都扫描完毕,就依次把栈中的数据pop出来进行计算
while (true) {
//if符号栈为空,则是结果
if (operStack.isEmpty()) {
break;
}
//不为空就是进行计算
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
//调用计算方法传递以上数据,得到运算结果
res = numStack.cal(num1, num2, oper);
numStack.push(res);//数据入栈
}
//打印结果
System.out.println(expression + "=" + numStack.pop());
}
}
//1.自定义栈
class ArrayStack2 {
//数组模拟栈
private int[] stack;
//栈最大值
private int maxSize;
//栈顶指针,初始化为-1
private int top = -1;
//2.构造器
public ArrayStack2(int maxSize) {
this.maxSize = maxSize;
//获取到栈的元素的个数
//创建栈,使用数组模拟的栈,就是创建一个数组
stack = new int[maxSize];
}
//判断栈满
//就是栈顶到了数组的最后一个下标 即 maxSize-1
public boolean isFull() {
return top == maxSize - 1;
}
//判断栈空
//就是栈顶指针等于-1
public boolean isEmpty() {
return top == -1;
}
//入栈
//参数就是入栈的值
public void push(int value) {
//判断条件了没有?
//首先判断栈是否满
if (isFull()) {
System.out.println("栈已经满了,请滚蛋!");
return;
}
//获取到入栈的值 ,就先栈顶指针后移一位
//然后把value加入到该栈顶指针指向的数组下标的位置中
top++;
stack[top] = value;
}
//出栈
//要求就返回一个值就是出栈的值
public int pop() {
//出栈前也要判断是否空,空就抛出异常
if (isEmpty()) {
//就是这个方法是需要返回值的,所以不能直接通过return;去终止方法
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.print(stack[i] + ",");
System.out.println();
}
}
//2.返回运算符的优先级
//使用数字大小决定优先级大小
//char型运算符的底层还是int型
public int priority(int oper) {
if (oper == '*' || oper == '/') {
return 1;
} else if (oper == '+' || oper == '-') {
return 0;
} else {
return -1;//假定只有加减乘除
}
}
//2.1先判断是否是运算符
public boolean isOper(char val) {
return val == '+' || val == '-' || val == '*' || val == '/';
}
//3.计算方法
public 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;
default:
break;
}
return result;
}
//4.增加一个方法,返回当前的栈顶的值用于判断
public int peek() {
return stack[top];
}
}
逆波兰计算器
思路:
1.这里是直接给出逆波兰表达式的,所以直接可以按照逆波兰的表达式计算顺序来计算
2.把逆波兰表达式使用集合存储,
3.使用集合中的栈直接创建栈
4.把集合中的数据逐个进栈,数字就 直接进栈,如果是符号就弹出两个数字进行运算
4.1计算的时候需要进行类型转换
5.计算结果再次入栈,栈中最后一个值就是结果
package com.Yjm.stack;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class PolandNotation {
public static void main(String[] args) {
//逆波兰表达式: "3 4 + 5 * 6 -"
String suffixExpression = "30 4 + 5 * 6 -";
//先转换逆波兰
List<String> list = getList(suffixExpression);
System.out.println(list);
//计算逆波兰
int calculate = calculate(list);
System.out.println(suffixExpression + "表达式的结果为 " + calculate);
}
//2.接收转换好的集合表达式进行计算
public static int calculate(List<String> getList) {
/* 1)从左至右扫描,将3和4压入堆栈;
2)遇到+运算符,因此弹出4和3 (4为栈项元素,3为次顶元素),计算出3+4的值,得7,再将7入栈; .
3)将5入栈;
4)接下来是x运算符,因此弹出5和7,计算出7x5=35,将35入栈;
5)将6入栈;
6)最后是-运算符,计算出35-6的值,即29,由此得出最终结果*/
//2.1创建栈
Stack<String> stack = new Stack<>();
//2.2数据入栈
//循环遍历获取集合中的每一个数据
for (String item : getList) {
//判断是否是数字,数数字就直接入栈
//使用正则表达式判断
//"\\d+":表示匹配多位数
if (item.matches("\\d+")) {
stack.push(item);
} else {
//当匹配到符号的时候就进行计算
//获取栈中的数据
int num1 = Integer.parseInt(stack.pop());
int num2 = Integer.parseInt(stack.pop());
int res = 0;
if (item.equals("+")) {
//弹出栈的两个数据进行运算
res = num1 + num2;
//注意是使用集合中的单个数据进行匹配
} else if (item.equals("-")) {
res = num2 - num1;
} else if (item.equals("*")) {
res = num2 * num1;
} else if (item.equals("/")) {
res = num2 / num1;
} else {
throw new RuntimeException();
}
//把得到的结果再次进行入栈
stack.push(res + "");
}
}
//返回结果
return Integer.parseInt(stack.pop());
}
//1.定义方法进行转换计算
public static List<String> getList(String suffixExpression) {
//1.1创建集合,用于存储逆波兰表达式
ArrayList<String> list = new ArrayList<>();
//通过循环遍历把表达式逐个储存进去
String[] str = suffixExpression.split(" ");
for (int i = 0; i < str.length; i++) {
list.add(str[i]);
}
return list;
}
}
//思路:
/*
* 1.这里是直接给出逆波兰表达式的,所以直接可以按照逆波兰的表达式计算顺序来计算
* 2.把逆波兰表达式使用集合存储,
* 3.使用集合中的栈直接创建栈
* 4.把集合中的数据逐个进栈,数字就 直接进栈,如果是符号就弹出两个数字进行运算
* 4.1计算的时候需要进行类型转换
* 5.计算结果再次入栈,栈中最后一个值就是结果
* */
中缀表达式转后缀表达式
中缀表达式转后缀表达式思路步骤分析
1 + (( 2+3 ) * 4) - 5
思路
-
初始化两个栈,运算符栈s1和存储中间结果的栈s2
-
从左至右扫描中缀表达式
-
遇到操作数就入栈s2
-
遇到运算符就比较与s1栈顶运算符的优先级
- 如果s1为空,或栈顶运算符为左括号"( ",则直接入s1
- 如果该运算符优先级高于栈顶运算符,也直接压入s1
- 如果该运算符优先级低于等于栈顶运算符,将s1栈顶的运算符弹出来并压入到s2中,再次回到4.1与s1新的栈顶运算符比较
-
遇到括号时:
- 如果是左括号"(",直接压入s1
- 如果是右括号")",则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止, 此时将这一对括号丢弃
-
重复步骤2到5,直到表达式的最右边
-
将s1中剩余的运算符依次弹出并压入s2
-
依次弹出s2的元素并输出, 结果的逆序则为后缀表达式
package com.Yjm.stack;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class PolandNotation {
public static void main(String[] args) {
//逆波兰表达式: "3 4 + 5 * 6 -"
String suffixExpression = "30 4 + 5 * 6 -";
//先转换逆波兰
List<String> list = getList(suffixExpression);
System.out.println(list);
//计算逆波兰
int calculate = calculate(list);
System.out.println(suffixExpression + "表达式的结果为 " + calculate);
//测试转换结果打印
List<String> strings = toInfixExpressionList("1+((2+3)*4)-5");
List<String> pfl = parseSuffixExpreesionList(strings);
System.out.println("后缀表达式: " + pfl);
//计算后缀表达式的结果
int calculate2 = calculate(pfl);
System.out.println(pfl + "的结果为:" + calculate2);
}
/*
1. 初始化两个栈,运算符栈s1和存储中间结果的栈s2
2. 从左至右扫描中缀表达式
3. 遇到操作数就入栈s2
4. 遇到运算符就比较与s1栈顶运算符的优先级
1. 如果s1为空,或栈顶运算符为**左括号**"( ",则直接入s1
2. 如果该运算符**优先级高于栈顶**运算符,也直接压入s1
3. 如果该运算符**优先级低于等于栈顶**运算符,将**s1栈顶的运算符**弹出来并压入到s2中,再次回到4.1与s1新的栈顶运算符比较
5. 遇到括号时:
1. 如果是左括号"(",直接压入s1
2. 如果是右括号")",则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止, 此时将这一对括号丢弃
6. 重复步骤2到5,直到表达式的最右边
7. 将s1中剩余的运算符依次弹出并压入s2
8. 依次弹出s2的元素并输出, 结果的**逆序**则为后缀表达式*/
public static List<String> parseSuffixExpreesionList(List<String> ls) {
//定义两个栈
Stack<String> s1 = new Stack<>();
ArrayList<String> s2 = new ArrayList<>();
//遍历中缀表达式
for (String item : ls) {
//如果是数字就直接入数栈2
if (item.matches("\\d+")) {
s2.add(item);
} else if (item.equals("(")) {//判断左右符号,如果是左括号就直接入栈1
s1.push(item);
} else if (item.equals(")")) {//如果是右括号就依次弹出s1的运算符,并压入s2,直到遇到左括号
while (true) {
if (!s1.peek().equals("(")) {//查看栈顶元素判断是否满足
//不是左括号就弹出,是左括号就跳出循环
s2.add(s1.pop());
} else //当是左括号就逃出
break;
}
s1.pop();//弹出右括号
} else {
//当是运算符就比较运算符的优先级
//把遍历到item和s1栈顶的运算符比较优先级,低于s1栈顶的优先级就将s1的运算符加入到s2中,再次转到4.1中重新比较,直到条件不成立
//s1.peek表示查到当前栈顶元素
while (s1.size() != 0 && Operation.getValue(s1.peek()) >= Operation.getValue(item)) {
//把s1的栈顶的元素压入s2
s2.add(s1.pop());
}
//比较之后当item的优先级高于栈顶**运算符,就压入s1
s1.push(item);
}
}
//将s1中剩余的运算符依次弹出并压入s2
while (s1.size() != 0) {
s2.add(s1.pop());
}
// 依次弹出s2的元素并输出, 结果的**逆序**则为后缀表达式
//如果是栈就要逆序输出也就是反向pop出来才可以
//但是这个是集合所以可以理解为从栈顶出来
return s2;
}
//方法:将 中缀表达式转换成对应的List
//1.转换成单个字符然后直接添加到集合中
public static List<String> toInfixExpressionList(String s) {
//创建集合
List<String> ls = new ArrayList<>();
//创建字符变量
char c;
//如何遍历字符串
//根据字符串长度遍历
//把值赋给字符 再转成字符串添加到集合中
//如果是多位数的话要另外判断!!!!!!!
//创建用于拼接的字符串
String str = "";
//定义索引
int i = 0;
//使用do while判断多位数
do {
//如果是运算符就直接加入到集合
if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57) {
ls.add(c + "");
i++;
} else {
//字符串要置空
str = "";
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.使用集合中的栈直接创建栈
* 4.把集合中的数据逐个进栈,数字就 直接进栈,如果是符号就弹出两个数字进行运算
* 4.1计算的时候需要进行类型转换
* 5.计算结果再次入栈,栈中最后一个值就是结果
* */
//2.接收转换好的集合表达式进行计算
public static int calculate(List<String> getList) {
/* 1)从左至右扫描,将3和4压入堆栈;
2)遇到+运算符,因此弹出4和3 (4为栈项元素,3为次顶元素),计算出3+4的值,得7,再将7入栈; .
3)将5入栈;
4)接下来是x运算符,因此弹出5和7,计算出7x5=35,将35入栈;
5)将6入栈;
6)最后是-运算符,计算出35-6的值,即29,由此得出最终结果*/
//2.1创建栈
Stack<String> stack = new Stack<>();
//2.2数据入栈
//循环遍历获取集合中的每一个数据
for (String item : getList) {
//判断是否是数字,数数字就直接入栈
//使用正则表达式判断
//"\\d+":表示匹配多位数
if (item.matches("\\d+")) {
stack.push(item);
} else {
//当匹配到符号的时候就进行计算
//获取栈中的数据
int num1 = Integer.parseInt(stack.pop());
int num2 = Integer.parseInt(stack.pop());
int res = 0;
if (item.equals("+")) {
//弹出栈的两个数据进行运算
res = num1 + num2;
//注意是使用集合中的单个数据进行匹配
} else if (item.equals("-")) {
res = num2 - num1;
} else if (item.equals("*")) {
res = num2 * num1;
} else if (item.equals("/")) {
res = num2 / num1;
} else {
throw new RuntimeException();
}
//把得到的结果再次进行入栈
stack.push(res + "");
}
}
//返回结果
return Integer.parseInt(stack.pop());
}
//1.定义方法进行转换计算
public static List<String> getList(String suffixExpression) {
//1.1创建集合,用于存储逆波兰表达式
ArrayList<String> list = new ArrayList<>();
//通过循环遍历把表达式逐个储存进去
String[] str = suffixExpression.split(" ");
for (int i = 0; i < str.length; i++) {
list.add(str[i]);
}
return list;
}
}
//添加判断符号优先级的类
class Operation {
private static int ADD = 1;
private static int SUB = 1;
private static int MUL = 2;
private static int DIV = 2;
//写一个方法,返回应对的优先级
public static int getValue(String operation) {
int result = 0;
switch (operation) {
case "+":
result = ADD;
break;
case "-":
result = SUB;
break;
case "*":
case "×":
result = MUL;
break;
case "/":
case "÷":
result = DIV;
break;
default:
break;
}
//返回结果
return result;
}
}