栈
需求引入
栈的介绍
栈的应用场景
出栈和入栈
package ArrayStack;
import java.net.Socket;
import java.util.Scanner;
public class ArrayStackDemo {
public static void main(String[] args) {
//测试一下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.printf("出栈的数据是%d\n",res);
}catch (Exception e){
System.out.println(e.getMessage());
}
break;
case "exit":
scanner.close();
loop = false;
break;
default:
break;
}
}
System.out.println("程序退出!");
}
}
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];
}
//栈满
public boolean isFull(){
return top == maxSize - 1;
}
//栈空
public boolean isEmpty(){
return top == -1;
}
//入栈-push
public void push(int value){
//先判断栈是否满
if (isFull()){
System.out.println("栈满");
return;
}
top++;
stack[top] = value;
}
//出栈-pop 讲栈顶的数据返回
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 = maxSize-1; i >= 0; i--) {
System.out.printf("stack[%d]=%d\n",i,stack[i]);
}
}
}
练习–链表模拟栈
好困看了别人的看懂了:
就是把链表过来进行使用,头插法相当于插入栈的顶部,出栈相当于…(自己想
class SingleLinkedListStack {
private Node top = new Node(-1);// 定义一个头指针代表栈顶
// 判断栈是否为空
public boolean isEmpty() {
return top.getNext() == null;
}
// 入栈,入栈的时候采用头插法
public void push(Node node) {
if (top.getNext() == null) {
// 第一个节点的插入
top.setNext(node);
return;
}
// 头插法
Node temp = top.getNext();// 定义一个临时变量使其指向top节点的下一个节点
top.setNext(node);
node.setNext(temp);
}
// 出栈
public void pop() {
if (top.getNext() == null) {
System.out.println("栈为空!不能出栈!");
return;
}
System.out.println("节点为:" + top.getNext().getValue());
top = top.getNext();
}
// 遍历栈
public void show() {
if (isEmpty()) {
System.out.println("栈为空!");
return;
}
Node temp = top;
while (temp.getNext() != null) {
System.out.println("节点为:" + temp.getNext().getValue());
temp = temp.getNext();
}
}
}
栈实现综合计算器
困的时候搞算法就是困难…
package ArrayStack;
public class Calculator {
public static void main(String[] args) {
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中
String keepNum = "";
//开始while喜欢扫描expression
while (true){
//依次得到expression的每一个字符
ch = expression.substring(index,index+1).charAt(0);//转为char
//判断ch是什么,然后做相应的处理
if (operStack.isOper(ch)){
//如果是运算符
//判断符号栈是否为空
if (!operStack.isEmpty()){
//如果当前的符号栈的优先级小于或等于栈中的操作符:
//需要从栈中pop出两个数,再pop一个符号,运算得到结果入数栈,然后当前操作符入符号栈
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 {
//如果是数 则直接进入数栈
//处理多位数 (而不是就压入一个字符就跑路
keepNum += ch;
//处理当处于最后一位数的越界情况
if (index == expression.length() -1){
numStack.push(Integer.parseInt(keepNum));
}else {
//判断下一个字符是不是数字
if (operStack.isOper(expression.substring(index+1,index+2).charAt(0))){
//如果下一位是运算符 则让这一位数入数栈
numStack.push(Integer.parseInt(keepNum));
//keepNum清空(很重要
keepNum = "";
}
}
}
//让index向后扫描 并判断是否到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);//入栈
}
//将数栈最后的数 pop出 就是结果
int res2 = numStack.pop();
System.out.printf("表达式%s = %d",expression,res2);
}
}
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];
}
//拓展方法
//返回当前栈顶的值,但没有做pop
public int peek(){
return stack[top];
}
//字符优先级返回
public int priority(int oper){
if (oper == '*' || oper == '/'){
return 1;
}else if (oper == '+' || oper == '-'){
return 0;
}else {
return -1;
}
}
//判断是不是一个运算符
public boolean isOper(char val){
return val =='+'||val =='-'||val == '*'||val == '/';
}
//计算
public int cal(int num1,int num2,int oper){
int res = 0;
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;
}
//栈满
public boolean isFull(){
return top == maxSize - 1;
}
//栈空
public boolean isEmpty(){
return top == -1;
}
//入栈-push
public void push(int value){
//先判断栈是否满
if (isFull()){
System.out.println("栈满");
return;
}
top++;
stack[top] = value;
}
//出栈-pop 讲栈顶的数据返回
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 = maxSize-1; i >= 0; i--) {
System.out.printf("stack[%d]=%d\n",i,stack[i]);
}
}
}
前缀、中缀、后缀规则
前缀(波兰表达式
中缀(人类最常用
逆波兰表达式实现计算器
介绍
思路:
前缀和后缀的区别
案例要求:
代码
package zhui;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class PolandNotation {
public static void main(String[] args) {
//声明一个波兰表达式
String suffixExpression = "3 4 + 5 * 6 -";
List<String> List = getListString(suffixExpression);
System.out.println("List = "+List);
System.out.println();
int res = calculate(List);
System.out.println("计算结果 = "+res);
}
//将逆波兰表达式,放入到ArrayList中
private static List<String> getListString(String suffixExpression) {
//将suffixExpression分割
String[] split = suffixExpression.split(" ");
List<String> list = new ArrayList<String>();
for (String ele:split){
// System.out.println("ele="+ele);
list.add(ele);
}
return list;
}
public static int calculate(List<String> ls){
//创建一个栈(就一个栈就够了
Stack<String> stack = new Stack<String>();
//遍历ls
for (String item:ls){
//利用正则表达式取出数
if (item.matches("\\d+")){
//匹配多位数
//入栈
stack.push(item);
}
//当item为操作符时
else {
//pop出两个数 运算后入栈(按后缀所以先弹出的是num2
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("*")){
res = num1*num2;
} else if (item.equals("/")){
res = num1/num2;
} else {
throw new RuntimeException("运算符有误!");
}
//int转String
//两种写法
stack.push(res+"");
// stack.push(""+res);
}
}
//最后留在stack中的数据是运算结果
return Integer.parseInt(stack.pop());
}
}
中缀转后缀
思想
main外部方法:
1.先将中缀表达式放入List中
//将中缀表达式转为对应的List
public static List<String> toInfixExpressionList(String s){
//定义一个List
List<String> ls = new ArrayList<String>();
int i = 0; //一个指针 用于遍历中缀表达式字符串
String str; //对多位数进行拼接
char c; //没遍历一个字符 就放入到c中
//do-while和while结果一样
// do {
// //如果c是一个非数字 直接加入ls中
// 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());
while (i < s.length()){
//如果c是一个非数字 直接加入ls中
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);
}
}
return ls;
}
main内方法:
String expression = "1+((2+3)*4)-5";
List<String> infixExpressionList = toInfixExpressionList(expression);
System.out.println(infixExpressionList);
2.再将List转为后缀
主方法
//方法:将得到的中缀表达式对应的List 转为 后缀表达式的List
public static List<String> parseSuffixExpressionList(List<String> ls){
//定义两个栈
Stack<String> s1 = new Stack<String>(); //符号栈
//说明:为什么不用Stack2
//因为s2这个栈 在整个转换过程中 没有pop操作 而且后面我们还需要逆序输出
//因此很麻烦 这里我们就不用Stack直接用List
List<String> s2 = new ArrayList<String>();
//遍历ls
for (String item:ls){
//如果是一个数 加入s2
if (item.matches("\\d+")){
s2.add(item);
}else if (item.equals("(")){
s1.push(item);
}else if (item.equals(")")){
//如果是右括号,则依次弹出s1栈顶的运算符 压入s2中 直到遇到左括号为止 此时将这一对括号丢弃
while (!s1.peek().equals("(")){
//peek去栈顶元素
s2.add(s1.pop());
}
s1.pop(); //将(弹出s1栈 等价于消除小括号
}
else {
//当遇到运算符时
//当item优先级小于等于s1栈顶运算符,将s1栈顶的运算符弹出并加入到s2中 再次转到(4.1)步骤
//与s1中新的栈顶运算符相比较
while (s1.size() != 0 && Operation.getValue(s1.peek()) >= Operation.getValue(item)){
s2.add(s1.pop());
}
//将item压入栈
s1.push(item);
}
}
//将s1中剩余的运算符依次弹出加入到s2中
while (s1.size() != 0){
s2.add(s1.pop());
}
return s2;//因为是放到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 res = 0;
switch(operation){
case "+":
res = ADD;
break;
case "-":
res = SUB;
break;
case "*":
res = MUL;
break;
case "/":
res = DIV;
break;
default:
System.out.println("不存在该运算符");
break;
}
return res;
}
}
main中
System.out.println();
List<String> suffixExpressionList = parseSuffixExpressionList(infixExpressionList);
System.out.println("后缀表达式对应的List" + suffixExpressionList);