栈:
- 栈是一个先入后出的有序列表
- 栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。
- 允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)。
栈的应用场景:
- 子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
- 处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
- 表达式的转换[中缀表达式转后缀表达式]与求值(实际解决)。
- 二叉树的遍历。
- 图形的深度优先(depth一first)搜索法。
数组模拟栈:
利用一个top指针指向栈顶的下标,默认为-1,每次元素入栈出栈的时候都相应的移动top指针,让它指向栈顶的下标。
数组模拟栈增删改查的思路:
- 元素入栈(push):当有数据加入到栈时, 让top指针向上移动:top++; stack[top] = data;
- 元素出栈(pop):当有元素出栈时,让top指针向下移动:int value = stack[top]; top–, return value
- 栈的遍历:也就相当于数组的遍历,当top = 数组的长度时,遍历结束。
代码实现:
package com.zxs.stack;
import java.util.Scanner;
/**
* @Classname ArrayStackDemo
* @Description TODO
* @Date 2021/5/12 13:37
* @Created by zxs
*/
public class ArrayStackDemo {
public static void main(String[] args) {
//测试一下ArrayStack 是否正确
//先创建一个ArrayStack对象->表示栈
ArrayStack stack = new ArrayStack(6);
String key = "";
boolean loop = true; //控制是否退出菜单
Scanner scanner = new Scanner(System.in);
while (loop) {
System.out.println("-----------请输入你的选择:---------------");
System.out.println("s:显示栈内数据");
System.out.println("push: 表示添加数据到栈(入栈)");
System.out.println("pop: 表示从栈取出数据(出栈)");
System.out.println("e:退出程序");
key = scanner.next();
switch (key) {
case "s":
stack.showStack();
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) {
// TODO: handle exception
System.out.println(e.getMessage());
}
break;
case "e":
scanner.close();
loop = false;
break;
default:
break;
}
}
System.out.println("程序退出~~~");
}
}
//用数组模拟创建栈
class ArrayStack {
private int arr[]; //模仿栈的数组
private int maxSize; //栈的最大长度
private int top = -1; //栈顶,默认为-1
//构造函数
public ArrayStack(int maxSize) {
this.maxSize = maxSize;
arr = new int[maxSize];
}
//是否为空
public boolean isEmpty() {
return top == -1;
}
//是否满
public boolean isFull() {
return top == maxSize - 1;
}
//添加数据(压栈 or 入栈)
public void push(int value) {
//判断是否满
if (isFull()) {
System.out.println("栈已满,添加失败!");
return;
}
//将top指向0
top++;
arr[top] = value;
System.out.println("添加成功~~~");
}
//取出数据,删除
public int pop() {
if (isEmpty()) {
throw new RuntimeException("栈为空,取出失败!");
}
int value = arr[top];
top--;
return value;
}
//查看数据,遍历
public void showStack() {
//判断是否为空
if (isEmpty()) {
System.out.println("栈为空,遍历失败!");
return;
}
//开始遍历
for (int i = top; i >= 0; i--) {
System.out.println("数据为:【" + arr[i] + "】");
}
}
}
链表模拟栈:
单向环形链表模拟栈,初始化一个first节点,利用节点中的data存储数据,first节点中的data默认为-1,并让fisrt节点的next和pre指向自己构成环形。之后添加第一个节点的时候,将data设置成第一个节点需要存储的元素。后面添加节点时,依次在first后面添加。
单向环形链表模拟栈增删改查的思路:
- 增加节点(push):判断当前链表是否为空,first节点的data=-1的时候为空,为空则设置data。不为空遍历找到链表的尾节点添加。
- 删除节点(pop):判断空,不为空则创建一个cur指针指向尾节点,将尾节点删除即可。
- 遍历节点:直接循环遍历即可。
代码实现:
package com.zxs.stack;
import java.util.Scanner;
/**
* @Classname CircleSingleLinkedListStackDemo
* @Description TODO
* @Date 2021/5/12 16:17
* @Created by zxs
*/
public class CircleSingleLinkedListStackDemo {
public static void main(String[] args) {
//初始化SingleLinkedListStack对象
CircleSingleLinkedListStack circleSingleLinkedListStack = new CircleSingleLinkedListStack(new StackNode(-1));
Scanner scanner = new Scanner(System.in);
//创建退出循环
boolean flag = true;
//接收数据
String key = "";
while (flag) {
System.out.println("---------请输入选择的功能:---------------");
System.out.println("s:遍历查看栈");
System.out.println("push:添加数据,压栈");
System.out.println("pop:取出数据,出栈");
System.out.println("e:退出程序");
key = scanner.next();
switch (key) {
case "s":
circleSingleLinkedListStack.showStack();
break;
case "push":
System.out.println("请输入要添加的数据:");
int value = scanner.nextInt();
circleSingleLinkedListStack.push(value);
break;
case "pop":
try {
int pop = circleSingleLinkedListStack.pop();
System.out.println("出栈的数据为:【" + pop + "】");
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case "e":
scanner.close();
flag = false;
break;
default:
System.out.println("输入有误,请重新输入!");
break;
}
}
}
}
//用单向环形链表模拟栈
class CircleSingleLinkedListStack {
//创建一个first节点
private StackNode first;
//初始化构成环形
public CircleSingleLinkedListStack(StackNode first) {
this.first = first;
first.next = first;
first.pre = first;
}
//判断空
public boolean isEmpty() {
return first.next == first;
}
//添加数据,入栈
public void push(int value) {
//创建节点
StackNode stackNode = new StackNode(value);
//创建指针
StackNode cur = first;
//遍历寻找到尾节点
while (true) {
if (cur.next == first) {
break;
}
cur = cur.next;
}
//让当前节点的next指向新节点
cur.next = stackNode;
//让新节点的next指向首节点
stackNode.next = first;
//让首节点的pre指向新节点
first.pre = stackNode;
//让新节点的pre指向当前节点
stackNode.pre = cur;
System.out.println("添加成功~~~");
}
//遍历查看栈内的数据
public void showStack() {
//判断空
if (isEmpty()) {
System.out.println("栈为空,遍历失败!");
return;
}
//创建指针,让指针指向尾节点
StackNode cur = first.pre;
while (true) {
System.out.println("栈内的数据为【" + cur.data + "】");
if (cur.pre == first) {
break;
}
cur = cur.pre;
}
}
//取出数据,出栈
public int pop() {
//创建指针,让cur指向要取出数据的节点
StackNode cur = first.pre;
//判断空
if (isEmpty()) {
throw new RuntimeException("栈为空,无法取出数据!");
}
int value = cur.data;
//将首节点的pre指向当前节点的上一个节点
first.pre = cur.pre;
//将当前节点的next指向首节点
cur.pre.next = first;
return value;
}
}
//栈节点
class StackNode {
public int data; //存放的数据
public StackNode next; //指向下一个栈节点
public StackNode pre; //指向上一个栈节点
public StackNode() {
}
public StackNode(int data) {
this.data = data;
}
@Override
public String toString() {
return "StackNode{" +
"data=" + data +
'}';
}
}
前缀、中缀、后缀表达式:
前缀表达式:前缀表达式又称波兰表达式,它的运算符位于操作数之前。举例说明:(3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6。
中缀表达式:中缀表达式就是常见的运算表达式,如(3+4)×5-6。
后缀表达式:后缀表达式又称逆波兰表达式,只是运算符位于操作数之后。举例说明: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 –。
前缀表达式:
- 前缀表达式求值步骤:从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素 和 次顶元素),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果
- 举例说明:(3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6
- 从右至左扫描,将6、5、4、3压入堆栈
- 遇到+运算符,因此弹出3和4(3为栈顶元素,4为次顶元素),计算出3+4的值,得7,再将7入栈
- 接下来是×运算符,因此弹出7和5,计算出7×5=35,将35入栈
- 最后是-运算符,计算出35-6的值,即29,由此得出最终结果
中缀表达式:
- 中缀表达式的求值是我们人最熟悉的,但是对计算机来说却不好操作。
- 在计算结果时,往往会将中缀表达式转成其它表达式来操作(一般转成后缀表达式)
后缀表达式:
- 后缀表达式求值步骤:从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 和 栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果
- 举例说明:(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,由此得出最终结果。
中缀表达式转换为后缀表达式:
-
后缀表达式适合计算式进行运算,但是人却不太容易写出来,尤其是表达式很长的情况下,因此在开发中,我们需要将中缀表达式转成后缀表达式。
-
转换步骤:
-
初始化两个栈:运算符栈s1和储存中间结果的栈s2。
-
从左至右扫描中缀表达式。
-
遇到操作数时,将其压s2。
-
遇到运算符时,比较其与s1栈顶运算符的优先级:
1)如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈。
2)否则,若优先级比栈顶运算符的高,也将运算符压入s1。
3)否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较。
-
遇到括号时:
1)如果是左括号“(”,则直接压入s1。
2)5)如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃。
-
重复步骤2至5,直到表达式的最右边。
-
将s1中剩余的运算符依次弹出并压入s2。
-
依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式。
-
数组模拟栈的简单计算器(中缀表达式):
- 只支持 + - * /,不支持()
- 支持多位数,但不支持小数
代码实现:
package com.zxs.stack;
/**
* @Classname Calculator
* @Description TODO
* @Date 2021/5/12 22:53
* @Created by zxs
*/
public class Calculator {
public static void main(String[] args) {
//创建一个数字栈和一个操作符栈
Stack numStack = new Stack(10);
Stack operStack = new Stack(10);
//定义表达式字符串
String expression = "30+2*6-2";
//定义接收参数的值
int num1;
int num2;
char oper; //运算符
char ch; //遍历到的字节
int res; //计算结果
int index = 0; //遍历扫描字符串的下标
String num=""; //用于拼接数字
//开始遍历扫描字符串
while (true) {
ch = expression.substring(index,index + 1).charAt(0);
//判断扫描到的字符是否是运算符
if (operStack.isOper(ch)) { //如果是
//那么判断操作符栈是否为空,为空则直接入栈
if (operStack.isEmpty()) {
//直接入栈
operStack.push(ch);
} else {
//否则,判断当前操作符的优先级是否小于等于运算符栈内的操作符
if (operStack.priority(ch) <= operStack.priority((char) operStack.getTop())) {
//如果小于等于,则从数字栈内连续pop两次,分别赋值给num1和num2
//再弹出栈内的运算符赋值给oper,进行计算将计算得出的结果入栈
num1 = numStack.pop();
num2 = numStack.pop();
oper = (char) operStack.pop();
//计算
res = numStack.calculator(num1,num2,oper);
//将计算得到的结果入栈
numStack.push(res);
//将当前遍历到的运算符入符号栈
operStack.push(ch);
} else {
//如果大于,则直接将运算符存入运算符栈内
operStack.push(ch); //此处char类型转换为int类型
}
}
} else {
//如果不是运算符,则是数字
num += ch; //进行拼接的字符
//判断数字的下一位是否是空
if (index == expression.length() - 1) {
//如果是,直接入栈
numStack.push(Integer.parseInt(num));
//清空num
num = "";
}else{
//判断,当前数字的下一位字符是否是数字
char c=expression.substring(index + 1, index + 2).charAt(0);
boolean flag = numStack.isOper(c);
if (flag) {
//如果是运算符,则直接入栈
numStack.push(Integer.parseInt(num));
//清空num
num = "";
}
}
}
index++;
if (index > expression.length() -1) {
break;
}
}
//再判断运算符栈是否为空,如果为空,则数字栈内的数字=最终计算结果
//不为空则将数字栈弹出两个,符号栈弹出一个,并计算
while (true) {
if (operStack.isEmpty()) {
break;
}
//不为空
num1 = numStack.pop();
num2 = numStack.pop();
oper = (char) operStack.pop();
//计算
res = numStack.calculator(num1, num2, oper);
//将计算结果存入数字栈中
numStack.push(res);
}
//输出计算结果
System.out.println(expression+" = "+numStack.getTop());
}
}
class Stack{
private int arr[]; //模仿栈的数组
private int maxSize; //栈的最大长度
private int top = -1; //栈顶,默认为-1
//构造函数
public Stack(int maxSize) {
this.maxSize = maxSize;
arr = new int[maxSize];
}
//是否为空
public boolean isEmpty() {
return top == -1;
}
//是否满
public boolean isFull() {
return top == maxSize - 1;
}
//添加数据(压栈 or 入栈)
public void push(int value) {
//判断是否满
if (isFull()) {
System.out.println("栈已满,添加失败!");
return;
}
//将top指向0
top++;
arr[top] = value;
System.out.println("添加成功~~~");
}
//取出数据,删除
public int pop() {
if (isEmpty()) {
throw new RuntimeException("栈为空,取出失败!");
}
int value=arr[top];
top--;
return value;
}
//查看数据,遍历
public void showStack(){
//判断是否为空
if (isEmpty()) {
System.out.println("栈为空,遍历失败!");
return;
}
//开始遍历
for (int i = top; i >= 0; i--) {
System.out.println("数据为:【"+arr[i]+"】");
}
}
//判断是否是操作符
public boolean isOper(char ch){
return ch == '+' || ch == '-' || ch == '*' || ch == '/';
}
//判断运算符的优先级,数字越大的优先级越高
public int priority(char ch){
if (ch == '+' || ch == '-') {
return 1;
} else{
return 2;
}
}
//计算,减法和除法要注意顺序
public int calculator(int num1,int num2,char ch){
int res=0;
switch (ch) {
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 int getTop(){
//判断栈是否空
if (top == -1) {
throw new RuntimeException("栈为空,获取失败!");
}
return arr[top];
}
}
逆波兰计算器完整版功能:
-
支持 + - * / ( ) 。
-
多位数,支持小数。
-
兼容处理, 过滤任何空白字符,包括空格、制表符、换页符。
代码实现:
package com.zxs.stack;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
import java.util.regex.Pattern;
public class ReversePolishMultiCalc {
/**
* 匹配 + - * / ( ) 运算符
*/
static final String SYMBOL = "\\+|-|\\*|/|\\(|\\)";
static final String LEFT = "(";
static final String RIGHT = ")";
static final String ADD = "+";
static final String MINUS= "-";
static final String TIMES = "*";
static final String DIVISION = "/";
/**
* 加減 + -
*/
static final int LEVEL_01 = 1;
/**
* 乘除 * /
*/
static final int LEVEL_02 = 2;
/**
* 括号
*/
static final int LEVEL_HIGH = Integer.MAX_VALUE;
static Stack<String> stack = new Stack<>();
static List<String> data = Collections.synchronizedList(new ArrayList<String>());
/**
* 去除所有空白符
* @param s
* @return
*/
public static String replaceAllBlank(String s ){
// \\s+ 匹配任何空白字符,包括空格、制表符、换页符等等, 等价于[ \f\n\r\t\v]
return s.replaceAll("\\s+","");
}
/**
* 判断是不是数字 int double long float
* @param s
* @return
*/
public static boolean isNumber(String s){
Pattern pattern = Pattern.compile("^[-\\+]?[.\\d]*$");
return pattern.matcher(s).matches();
}
/**
* 判断是不是运算符
* @param s
* @return
*/
public static boolean isSymbol(String s){
return s.matches(SYMBOL);
}
/**
* 匹配运算等级
* @param s
* @return
*/
public static int calcLevel(String s){
if("+".equals(s) || "-".equals(s)){
return LEVEL_01;
} else if("*".equals(s) || "/".equals(s)){
return LEVEL_02;
}
return LEVEL_HIGH;
}
/**
* 匹配
* @param s
* @throws Exception
*/
public static List<String> doMatch (String s) throws Exception{
if(s == null || "".equals(s.trim())) throw new RuntimeException("data is empty");
if(!isNumber(s.charAt(0)+"")) throw new RuntimeException("data illeagle,start not with a number");
s = replaceAllBlank(s);
String each;
int start = 0;
for (int i = 0; i < s.length(); i++) {
if(isSymbol(s.charAt(i)+"")){
each = s.charAt(i)+"";
//栈为空,(操作符,或者 操作符优先级大于栈顶优先级 && 操作符优先级不是( )的优先级 及是 ) 不能直接入栈
if(stack.isEmpty() || LEFT.equals(each)
|| ((calcLevel(each) > calcLevel(stack.peek())) && calcLevel(each) < LEVEL_HIGH)){
stack.push(each);
}else if( !stack.isEmpty() && calcLevel(each) <= calcLevel(stack.peek())){
//栈非空,操作符优先级小于等于栈顶优先级时出栈入列,直到栈为空,或者遇到了(,最后操作符入栈
while (!stack.isEmpty() && calcLevel(each) <= calcLevel(stack.peek()) ){
if(calcLevel(stack.peek()) == LEVEL_HIGH){
break;
}
data.add(stack.pop());
}
stack.push(each);
}else if(RIGHT.equals(each)){
// ) 操作符,依次出栈入列直到空栈或者遇到了第一个)操作符,此时)出栈
while (!stack.isEmpty() && LEVEL_HIGH >= calcLevel(stack.peek())){
if(LEVEL_HIGH == calcLevel(stack.peek())){
stack.pop();
break;
}
data.add(stack.pop());
}
}
start = i ; //前一个运算符的位置
}else if( i == s.length()-1 || isSymbol(s.charAt(i+1)+"") ){
each = start == 0 ? s.substring(start,i+1) : s.substring(start+1,i+1);
if(isNumber(each)) {
data.add(each);
continue;
}
throw new RuntimeException("data not match number");
}
}
//如果栈里还有元素,此时元素需要依次出栈入列,可以想象栈里剩下栈顶为/,栈底为+,应该依次出栈入列,可以直接翻转整个stack 添加到队列
Collections.reverse(stack);
data.addAll(new ArrayList<>(stack));
System.out.println(data);
return data;
}
/**
* 算出结果
* @param list
* @return
*/
public static Double doCalc(List<String> list){
Double d = 0d;
if(list == null || list.isEmpty()){
return null;
}
if (list.size() == 1){
System.out.println(list);
d = Double.valueOf(list.get(0));
return d;
}
ArrayList<String> list1 = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
list1.add(list.get(i));
if(isSymbol(list.get(i))){
Double d1 = doTheMath(list.get(i - 2), list.get(i - 1), list.get(i));
list1.remove(i);
list1.remove(i-1);
list1.set(i-2,d1+"");
list1.addAll(list.subList(i+1,list.size()));
break;
}
}
doCalc(list1);
return d;
}
/**
* 运算
* @param s1
* @param s2
* @param symbol
* @return
*/
public static Double doTheMath(String s1,String s2,String symbol){
Double result ;
switch (symbol){
case ADD : result = Double.valueOf(s1) + Double.valueOf(s2); break;
case MINUS : result = Double.valueOf(s1) - Double.valueOf(s2); break;
case TIMES : result = Double.valueOf(s1) * Double.valueOf(s2); break;
case DIVISION : result = Double.valueOf(s1) / Double.valueOf(s2); break;
default : result = null;
}
return result;
}
public static void main(String[] args) {
//String math = "9+(3-1)*3+10/2";
String math = "12.8 + (2 - 3.55)*4+10/5.0";
try {
doCalc(doMatch(math));
} catch (Exception e) {
e.printStackTrace();
}
}
}