一、栈的介绍
1.1 栈是一个先入后出的有序列表。
1)数据的添加和删除都在同一端,即入口和出口是同一个
2)出口称为【栈顶】,另一端就称为【栈底】
1.2 Java Vector类
1)Vector 类实现了一个动态数组。和 ArrayList 很相似,但是两者是不同的
2)Vector 是同步访问的
3)Vector 包含了许多传统的方法,这些方法不属于集合框架
4)Vector 主要用在事先不知道数组的大小,或者只是需要一个可以改变大小的数组的情况。
5)系统栈Stack就是用Vector实现的
二、数组实现栈
2.1 可行性分析
因为栈是一种有序列表,所以可以使用数组结构来是实现。
2.2 逻辑分析
1)定义一个变量top,初始化为-1
2)入栈:
1)top++
2)stack[top] = data
3)出栈:
1)先将栈顶元素取出来,并存储到一个变量
2)top—
3)返回存储栈顶元素的变量
三、单向链表实现栈
将新节点每次都添加到链表的最前端即可。
四、前缀、中缀、后缀表达式
4.1 前缀(prefix)表达式
1)前缀表达式又称为波兰表达式,运算符在操作数之前
2)如何计算前缀表达式的值?
1)反向遍历
2)前缀表达式就是【先把所有的数先压入堆栈】,【然后再扫描所有的操作符】,【在数入栈的过程中不进行运算】
3)前缀表达式就是根据运算符优先级写好的顺序,所以不需要计算机去判断优先级了。
4.2 中缀(infix)表达式
1)中缀表达式就是常见的运算表达式
2)中缀表达式的求值对于人来讲是最简单的,但是对于计算机来讲,却不太好操作。
3)中缀表达式的【麻烦之处】在于:需要判断当前运算符的优先级和符号栈内的优先级大小
1)(3+4)*5-6应该先算3+4
2)3+4*5-6应该先算4*5
3)上面两个表达式,对于人来说,很容易理解,但是对于计算机来讲,【必须要判断运算符的优先级才可以】
4.3 后缀表达式
1)后缀表达式又称逆波兰表达式,与前缀表达式相似,只是【运算符在操作数之后 】
2)操作数在前,运算符在后,前向遍历
3)后缀表达式就是在数入栈的过程中,同时进行运算
4)后缀表达式也是根据运算符优先级大小写好的顺序,所以在计算的时候,计算机不需要再判断优先级了
5)后缀表达式的计算就是省去了中缀表达式中运算符的优先级判断过程
6)简单记
因为计算机比较难处理的是运算符,所以【前缀】就是【运算符】在操作符【前面】,【后缀】就是在操作符【后面】。
7)中缀表达式转后缀表达式(重要)
1)初始化两个栈:运算符栈operStack和存储中间结果的栈midStack
2)从左到右遍历中缀表达式
3)遇到操作数时,直接压入midStack
4)遇到运算符时:
1)如果符号栈为空,则直接入栈
2)如果当前为左括号,则直接入栈
3)如果符号栈栈顶为左括号,则直接入栈
4)如果当前为右括号,则弹出符号栈栈顶元素,并压入中间栈
5)继续弹出符号栈中的一个元素,即该右括号对应的左括号
6.1)如果当前运算符优先级小于或等于符号栈栈顶运算符的优先级,则将符号栈内运算符弹出,并压入中间栈
6.2)然后,将当前运算符入符号栈
7)如果以上情况都不满足,则直接入栈
五、Java代码
5.1 数组实现栈
public class ArrayStack {
private int maxSize; //表示栈的大小
private int[] stack; //数组,用数组模拟栈,数据就放在该数组中
private int top = -1; //top为栈顶,初始化为-1,表示没有数据
/**
* 数组模拟栈的构造方法
* @param maxSize 表示栈的大小
*/
public ArrayStack(int maxSize){
this.maxSize = maxSize;
stack = new int[this.maxSize]; //数组初始化,这样才可以放数据
}
/**
* 判断栈满
* @return 栈满返回true
*/
public boolean isFull(){
return top == maxSize - 1;
}
/**
* 判断栈空
* @return 栈空返回true
*/
public boolean isEmpty(){
return top == -1;
}
/**
* 入栈-push
* @param value 需要入栈的数据
*/
public void push(int value){
if (isFull()){
System.out.println("栈满");
return;
}else {
top++;
stack[top] = value;
}
}
/**
* 出栈-pop,就是将栈顶的数据返回
* @return 返回的是栈顶的数据
*/
public int pop(){
if (isEmpty()){
//因为有返回值,所以选择抛出异常的方式来解决
throw new RuntimeException("栈空");
}else {
int value = stack[top];
top--;
return value;
}
}
/**
* 遍历栈
* 遍历时,需要从栈顶开始显示数据
*/
public void show(){
if (isEmpty()){
System.out.println("栈空");
return;
}else {
for (int i = top; i >= 0; i--){
System.out.printf("stack[%d]=%d\n", i, stack[i]);
}
}
}
}
5.2 单向链表实现栈
新添加的元素直接添加到链表的【最前端】,然后正常遍历,这样就实现了【先入后出】。
public class LinkedListStack {
private int maxSize; //栈的大小
private Node top; //定义一个节点,指向栈顶
private Node head = new Node(-1, -1);
/**
* 构造方法
*/
public LinkedListStack(int maxSize){
this.maxSize = maxSize;
top = null;
}
/**
* 栈满
*/
public boolean isFull(){
int length = 0;
try {
length = getLength();
}catch (Exception e){
System.out.println(e.getMessage());
}
return length == maxSize;
}
/**
* 栈空
*/
public boolean isEmpty(){
return top == null;
}
/**
* 入栈
* 将新节点添加带链表的最前位置
* 将top指向新添加的节点
*/
public void push(Node newNode){
if (isFull()){
System.out.println("栈满");
return;
}
newNode.setNext(head.getNext());
head.setNext(newNode);
top = newNode;
}
/**
* 获取链表有效节点个数
*/
public int getLength(){
if (head.getNext() == null){
throw new RuntimeException("链表为空!");
}else {
Node temp = head.getNext();
int length = 0;
while (temp != null){
length++;
temp = temp.getNext();
}
return length;
}
}
/**
* 出栈
* 即删除链表中的节点,从最后一个节点开始
*/
public Node pop(){
if (isEmpty()){
throw new RuntimeException("栈空");
}
Node temp = top;
if (top != null){
top = top.getNext();
}
return temp;
}
/**
* 遍历栈
*/
public void list(){
if (isEmpty()){
System.out.println("链空");
return;
}
Node temp = head.getNext();
while (true){
if (temp == null){
break;
}else {
System.out.println(temp);
temp = temp.getNext();
}
}
}
}
5.3 中缀计算器
直接传入一个中缀表达式,可以实现多位数、带括号的运算。
public class Calculator03 {
public static void main(String[] args) {
//创建一个表达式
// String expression = "2-(1+2)*3";
String expression = "(10-2)*2+12-6";
//创建两个栈:数栈和符号栈
ArrayStack numStack = new ArrayStack(15);
ArrayStack operStack = new ArrayStack(15);
//index用于遍历表达式
int index = 0;
int num1 = 0;
int num2 = 0;
//对于字符类型(char)来说,在计算之前,char会被提升成为int,然后在进行计算。
int oper = 0;
int res = 0;
char ch = ' '; //将每次扫描得到的char存到ch
String str = ""; //用来拼接多位数
while (true) {
//遍历expression
ch = expression.substring(index, index + 1).charAt(0);
if (!operStack.isOper(ch)) {
//如果当前字符是数字
str += ch;
if (index == expression.length() - 1) {
numStack.push(Integer.parseInt(str));
} else if (operStack.isOper(expression.substring(index + 1, index + 2).charAt(0))) {
numStack.push(Integer.parseInt(str));
str = "";
}
} else {
//如果当前字符是运算符
while (!operStack.isEmpty()) {
//如果符号栈不为空,则将当前运算符和符号栈栈顶的运算符进行优先级比较
if (operStack.peak() == '('){
//如果符号栈栈顶为左括号,不管当前符号栈内是否有其他运算符,直接退出循环,将当前运算符入符号栈
break;
}else if (ch == '('){
//如果当前运算符为左括号,直接退出循环,并将其入符号栈
break;
}else if (ch == ')'){
//如果当前字符为有括号,则应该从数栈和符号栈取出相应数字和运算符进行运算,并将结果入数栈
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
res = numStack.cal(num1, num2, oper);
numStack.push(res);
//运算完结果后,符号栈内还存在左括号,直接弹出
operStack.pop();
break;
}else if (operStack.priority(ch) <= operStack.priority(operStack.peak())){
//如果当前运算符的优先级比符号栈内的运算符小,则应该先进行计算,然后保证符号栈内没有比当前ch优先级高的运算符以后,在将ch入符号栈
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
res = numStack.cal(num1, num2, oper);
numStack.push(res);
}else {
break;
}
}
//当退出while循环后,符号栈内存在的运算符优先级是不同的
operStack.push(ch);
if (operStack.peak() == ')'){
//如果符号栈栈顶为有括号,则直接弹出,因为遇到右括号的时候,进行了计算,并将左括号弹出了,右括号如果在符号栈的话,会影响后续计算
operStack.pop();
}
}
index++;
if (index >= expression.length()) {
break;
}
}
while (true){
//获取表达式的结果
if (operStack.isEmpty()){
//如果符号栈为空,则此时数栈内的数值就是表达式的结果
res = numStack.pop();
System.out.printf("表达式%s = %d\n", expression, res);
break;
}else {
//符号栈内存在不同优先级的运算符
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
res = numStack.cal(num1, num2, oper);
numStack.push(res);
}
}
}
}
5.4 后缀计算器
传入一个中缀表达式,可以自动计算其后缀表达式,并根据后缀表达式进行计算。
public class SuffixCalculator02 {
public static void main(String[] args) {
//定义一个中缀表达式
String infixExpression = "1+(2*(2+3))*(4-1)";
CalSuffix cs = new CalSuffix();
//调用方法计算对应的后缀表达式
String suffixExpression = cs.calSuffix(infixExpression);
System.out.printf("中缀表达式%s对应的后缀表达式为%s\n", infixExpression, suffixExpression); //中缀表达式1+(2*(2+3))*(4-1)对应的后缀表达式为1223+*41-*+
//将String后缀表达式转换为List集合
List<String> suffixList = getList(suffixExpression);
System.out.println("suffixList = " + suffixList); //suffixList = [1, 2, 2, 3, +, *, 4, 1, -, *, +]
try {
//调用方法计算后缀表达式的结果
int val = calculate(suffixList);
System.out.printf("后缀表达式 %s 的结果为:%d\n", suffixExpression, val); //后缀表达式 1223+*41-*+ 的结果为:31
}catch (Exception e){
e.getMessage();
}
}
/**
* 将一个后缀表达式,依次将数据和运算符放入到ArrayList集合中
* @param suffixExpression 后缀表达式
* @return ArrayList集合
*/
public static List<String> getList(String suffixExpression){
//对传递进来的字符串进行分割
String[] s = suffixExpression.split("");
List<String> list = new ArrayList<>();
for (int i = 0; i < s.length; i++) {
list.add(s[i]);
}
return list;
}
/**
* 计算逆波兰(后缀)表达式结果
* @param list 存储后缀表达式的List集合
* @return 后缀表达式的结果
*/
public static int calculate(List<String> list){
//创建一个栈,只需要一个栈就可以
Stack<Integer> stack = new Stack<>();
//遍历list
for (String s : list) {
if (s.matches("\\d+")){
stack.push(Integer.parseInt(s));
}else {
//如果是一个运算符的话,则从栈中取出两个数进行运算,并将结果入栈
int num2 = stack.pop();
int num1 = stack.pop();
int res = 0;
if (Objects.equals(s, "+")){
res = num1 + num2;
}else if (Objects.equals(s, "-")){
res = num1 - num2;
}else if (Objects.equals(s, "*")){
res = num1 * num2;
}else if (Objects.equals(s, "/")){
res = num1 / num2;
}else if (Objects.equals(s, "%")){
res = num1 % num2;
}
else {
throw new RuntimeException("无效运算符!");
}
stack.push(res);
}
}
//for循环结束后,栈中的数据就是表达式的结果
return stack.pop();
}
}
其中,定义了一个CalSuffix类,其中的成员方法可以计算中缀表达式对应的后缀表达式。
核心思想:遇到【右括号】时:
1)将符号栈中栈顶运算符弹出,并压入中间栈,
2)同时,符号栈在执行一次弹出操作,将该右括号对应的左括号弹出。
public class CalSuffix {
/**
* 构造方法
*/
public CalSuffix() {
}
/**
* 计算后缀表达式的方法
* @param infixExpression 中缀表达式
* @return 后缀表达式
*/
public String calSuffix(String infixExpression){
String[] split = infixExpression.split("");
//创建一个ArrayList集合,用于存放中缀表达式,因为list的遍历比较方便
List<String> list = new ArrayList<>();
for (int i = 0; i < split.length; i++) {
list.add(split[i]);
}
Stack<String> operStack = new Stack<>(); //operStack用于存放运算符
Stack<String> midStack = new Stack<>(); //midStack用于存储中间结果
String suffixExpression = "";
for (String item : list) {
if (item.matches("\\d+")){
//如果是数字
midStack.push(item);
}else {
//如果是运算符
if (operStack.isEmpty()){
operStack.push(item);
}else if (Objects.equals(item, "(")){
operStack.push(item);
}else if (Objects.equals(operStack.peek(), "(")){
operStack.push(item);
}else if (Objects.equals(item, ")")){
midStack.push(operStack.pop());
operStack.pop();
}else if (Priority.getPriority(item) <= Priority.getPriority(operStack.peek())){
//如果当前运算符优先级小于或等于符号栈栈顶运算符的优先级,则将符号栈内运算符弹出,并压入中间栈
midStack.push(operStack.pop());
//然后,将当前运算符入符号栈
operStack.push(item);
}else {
operStack.push(item);
}
}
}
//当退出for循环后,符号栈中存储的是中缀表达式最后一个运算符
while (!midStack.isEmpty()){
//将中间栈的数据一次弹出并放入符号栈,最终符号栈中的数据就是后缀表达式
operStack.push(midStack.pop());
}
while (!operStack.isEmpty()){
//依次弹出符号栈中的元素
suffixExpression += operStack.pop();
}
return suffixExpression;
}
}
也定义了一个Priority类,用于判断运算符的优先级。
public class Priority {
/**
* 构造方法
*/
public Priority() {
}
/**
* 获取运算符的优先级
* @param oper 运算符
* @return 优先级
*/
public static int getPriority(String oper){
int res = -1;
if (Objects.equals(oper, "+") || Objects.equals(oper, "-")){
res = 0;
}else if (Objects.equals(oper, "*") || Objects.equals(oper, "/") || Objects.equals(oper, "%")){
res = 1;
}else {
System.out.println("无效运算符!");
}
return res;
}
}