栈
栈的介绍
- 栈的英文为(stack)。
- 栈是一个先入后出(FILO-First In Last Out)的有序列表。
- 栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和- 删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)。
- 根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除。
出栈(pop)和入栈(push)的概念(如图所示):
用数组模拟栈的思路分析图
代码示例:
//数组模拟栈
public class ArrayStackDemo {
public static void main(String[] args) {
//测试栈
ArrayStack arrayStack = 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":
arrayStack.show();
break;
case "exit":
scanner.close();
loop=false;
break;
case "push":
System.out.println("请输入一个数");
int value=scanner.nextInt();
arrayStack.push(value);
break;
case "pop":
try {
System.out.println("出栈的数:"+arrayStack.pop());
} catch (Exception e) {
e.printStackTrace();
}
break;
default:
break;
}
}
System.out.println("程序退出了");
}
}
//数组模拟栈
class ArrayStack {
private int maxSize; //栈的大小
private int[] stack; //用来模拟栈的数组
private int top = -1;//栈顶,初始化为-1
public ArrayStack(int maxSize) {
this.maxSize = maxSize;
stack = new int[maxSize];
}
//栈满
public boolean isFull() {
return top == maxSize - 1;
}
//栈空
public boolean isEmpty() {
return top == -1;
}
//入栈
public void push(int value) {
//判断是否栈满
if (isFull()) {
System.out.println("满了");
return;
}
top++;
stack[top] = value;
}
//出栈
public int pop() {
//判断是否栈空
if (isEmpty()) {
throw new RuntimeException("栈空");
}
int value = stack[top];
top--;
return value;
}
//遍历 需要从栈顶开始
public void show() {
if (isEmpty()) {
System.out.println("空的");
return;
}
for (int i = top; i >= 0; i--) {
System.out.printf("stack[%d]=%d\n", i, stack[i]);
}
}
}
栈实现之综合计算器(中缀表达式)
中缀表达式
- 中缀表达式就是常见的运算表达式,如(3+4)×5-6
- 中缀表达式的求值是我们人最熟悉的,但是对计算机来说却不好操作(前面我们讲的案例就能看的这个问题),因此,在计算结果时,往往会将中缀表达式转成其它表达式来操作(一般转成后缀表达式.)
思路图解
代码示例
/用栈计算表达式运算
public class Calculator {
public static void main(String[] args) {
//表达式
String experssion = "20+3*21/5-11";
//创建两个栈 数栈 符号栈
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 = 0;//将每次扫描得到char保存到ch
String keepNum = "";
while (true) {
ch = experssion.substring(index, index + 1).charAt(0);
if (numStack.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 = operStack.cal(num1, num2, oper);
numStack.push(res);
operStack.push(ch);
} else {
//如果当前的操作符的优先级大于栈中的操作符, 就直接入符号栈.
operStack.push(ch);
}
} else {
//如果为空直接入符号栈
operStack.push(ch);
}
} else {
//如果是数,则直接入数栈
//numStack.push(ch - 48);//对比ascii表 数字要减48
//考虑多位数的情况
keepNum+=ch;
if (index==experssion.length()-1){
numStack.push(Integer.parseInt(keepNum));
}else {
if (operStack.isOper(experssion.substring(index + 1, index + 2).charAt(0))) {
numStack.push(Integer.parseInt(keepNum));
keepNum = "";//记得清空
}
}
}
//让index + 1, 并判断是否扫描到expression最后.
index++;
if (index >= experssion.length()) {
break;
}
}
//当表达式扫描完毕,就顺序的从 数栈和符号栈中pop出相应的数和符号,并运行
while (true) {
//如果符号栈为空,则计算到最后的结果, 数栈中只有一个数字【结果】
if (operStack.isEmpty()) {
break;
}
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
res = operStack.cal(num1, num2, oper);
numStack.push(res);
}
System.out.println("表达式的结果:" + experssion + " = " + numStack.pop());
}
}
//数组模拟栈 扩展
class ArrayStack2 {
private int maxSize; //栈的大小
private int[] stack; //用来模拟栈的数组
private int top = -1;//栈顶,初始化为-1
public ArrayStack2(int maxSize) {
this.maxSize = maxSize;
stack = new int[maxSize];
}
//栈满
public boolean isFull() {
return top == maxSize - 1;
}
//返回当前栈顶的值 但不出栈
public int peek() {
return stack[top];
}
//栈空
public boolean isEmpty() {
return top == -1;
}
//入栈
public void push(int value) {
//判断是否栈满
if (isFull()) {
System.out.println("满了");
return;
}
top++;
stack[top] = value;
}
//出栈
public int pop() {
//判断是否栈空
if (isEmpty()) {
throw new RuntimeException("栈空");
}
int value = stack[top];
top--;
return value;
}
//遍历 需要从栈顶开始
public void show() {
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 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;
}
}
逆波兰计算器(后缀表达式) 带中缀转后缀表达式
后缀表达式
- 后缀表达式又称逆波兰表达式,与前缀表达式相似,只是运算符位于操作数之后
- 中举例说明: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 –
思路分析
例如: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 - , 针对后缀表达式求值步骤如下:
从左至右扫描,将3和4压入堆栈;
遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
将5入栈;
接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
将6入栈;
最后是-运算符,计算出35-6的值,即29,由此得出最终结果
大家看到,后缀表达式适合计算式进行运算,但是人却不太容易写出来,尤其是表达式很长的情况下,因此在开发中,我们需要将 中缀表达式转成后缀表达式。
思路图解
代码示例
//逆波兰表达式计算器 带中缀转后缀表达式
public class PolandNotation {
public static void main(String[] args) {
//完成将一个中缀表达式转成后缀表达式的功能
//1. 1+((2+3)×4)-5 => 转成 1 2 3 + 4 × + 5 –
//2. 因为直接对str 进行操作,不方便,因此 先将 "1+((2+3)×4)-5" =》 中缀的表达式对应的List
// 即 "1+((2+3)×4)-5" => ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]
//3. 将得到的中缀表达式对应的List => 后缀表达式对应的List
// 即 ArrayList [1,+,(,(,2,+,3,),*,4,),-,5] =》 ArrayList [1,2,3,+,4,*,+,5,–]
String infixExpression = "1+((2+3)*4)-5";
List<String> ls = infixExpressionToList(infixExpression);
System.out.println(ls);
List<String> ls2 = parseSuffixExpressionList(ls);
System.out.println(ls2);
System.out.println(calculate(ls2));
/**
* 思路
* 1.将逆波兰表达式放入到ArrayList中
* 2.遍历ArrayList配合栈 完成计算
*/
// String suffixExpression = "3 4 + 5 * 6 -";
// List<String> listString = getListString(suffixExpression);
// int calculate = calculate(strings1);
// System.out.println(calculate);
}
//将中缀表达式对应的List=>后缀表达式对应List
public static List<String> parseSuffixExpressionList(List<String> ls) {
Stack<String> s1 = new Stack<>(); //符号栈
//说明:因为s2 这个栈,在整个转换过程中,没有pop操作,而且后面我们还需要逆序输出
//因此比较麻烦,这里我们就不用 Stack<String> 直接使用 List<String> s2
//Stack<String> s2 = new Stack<String>(); // 储存中间结果的栈s2
List<String> s2 = new ArrayList<>(); // 储存中间结果的栈s2
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("(")){
s2.add(s1.pop());
}
s1.pop();//去掉"("
}else{
//当item的优先级小于等于s1栈顶运算符, 将s1栈顶的运算符弹出并加入到s2中,再次与s1中新的栈顶运算符相比较
while (s1.size()>0&&Operation.getValue(item)<=Operation.getValue(s1.peek())){
s2.add(s1.pop());
}
s1.push(item);
}
}
//将s1中剩余的运算符依次弹出并加入s2
while (s1.size()>0){
s2.add(s1.pop());
}
return s2;//注意因为是存放到List, 因此按顺序输出就是对应的后缀表达式对应的List
}
//将中缀表达式转换成对应的List
public static List<String> infixExpressionToList(String infixExpression) {
List<String> ls = new ArrayList<>();
int i = 0;
char ch;
String str;
do {
//非数字
if ((ch = infixExpression.charAt(i)) < 48 || (ch = infixExpression.charAt(i)) > 57) {
ls.add(ch + "");
i++;
} else {
str = "";
while (i < infixExpression.length() && (ch = infixExpression.charAt(i)) >= 48 && (ch = infixExpression.charAt(i)) <= 57) {
str += ch;
i++;
}
ls.add(str);
}
} while (i < infixExpression.length());
return ls;
}
//逆波兰表达式放入到ArrayList中
public static List<String> getListString(String suffixExpression) {
String[] split = suffixExpression.split(" ");
List<String> list = new ArrayList<>();
for (String s : split) {
list.add(s);
}
return list;
}
//完成逆波兰表达式的运算
public static int calculate(List<String> ls) {
Stack<String> stack = new Stack<>();
for (String item : ls) {
//正则表达式 匹配多位数
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 = num1 * num2;
} else if (item.equals("/")) {
res = num2 / num1;
} else {
throw new RuntimeException("运算符有误");
}
stack.push(res + "");
}
}
return Integer.parseInt(stack.pop());
}
}
//编写一个Operation 返回一个运算符 对应的优先级
class Operation {
private static int ADD = 1;
private static int SUB = 1;
private static int MUL = 1;
private static int DIV = 1;
public static int getValue(String operation) {
int result = 0;
switch (operation) {
case "+":
result = ADD;
break;
case "-":
result = SUB;
break;
case "*":
result = MUL;
break;
case "/":
result = DIV;
break;
default:
System.out.println("符号有误");
break;
}
return result;
}
}