Chapter 4 栈
文章目录
4.1 栈
4.1.1 栈的介绍
- 栈(Stack)是一个先入后出(FILO)的有序列表
- 栈是限制线性表中元素插入和删除只能在线性表的同一端进行的一种特殊线性表,允许插入和删除的一端为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)
- 根据栈的定义可知,最先放入栈中的元素在栈底,最后放入栈的元素在栈顶,而删除元素刚好相反
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LuAef9bK-1638952873282)(C:/Users/80653/AppData/Roaming/Typora/typora-user-images/image-20211203195112179.png)]
4.1.2 数组模拟栈
思路:
- 定义一个top指向栈顶,初始化为-1
- 入栈时 top++;stack[top]=data;
- 出栈时 int value = stack[top];top–;return value;
实现:
public class ArrayStackTest {
public static void main(String[] args) {
ArrayStack arrayStack = new ArrayStack(5);
arrayStack.push(1);
arrayStack.push(2);
arrayStack.push(3);
arrayStack.push(4);
arrayStack.push(5);
arrayStack.push(5);
arrayStack.listArrayStack();
try {
arrayStack.pop();
arrayStack.pop();
arrayStack.pop();
arrayStack.pop();
arrayStack.pop();
}catch (RuntimeException e){
System.out.println(e.getMessage());
}
arrayStack.listArrayStack();
}
}
class ArrayStack{
private int maxSize;
private int stack[];
private int top = -1;
public ArrayStack(int maxSize){
this.maxSize = maxSize;
stack = new int[maxSize];
}
//判断栈空
public boolean isEmpty(){
return top==-1;
}
//判断栈满
public boolean isFull(){
return top==maxSize-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 listArrayStack(){
if(isEmpty()){
System.out.println("栈空,无法遍历");
return;
}
for(int i=top;i>=0;--i){
System.out.printf("stack[%d]=%d\n",i,stack[i]);
}
}
}
4.1.3 练习:用链表模拟栈
import java.util.Stack;
public class LinkedListStackTest {
public static void main(String[] args) {
Data data1 = new Data(1);
Data data2 = new Data(2);
Data data3 = new Data(3);
Data data4 = new Data(4);
Data data5 = new Data(5);
LinkedListStack linkedListStack = new LinkedListStack();
linkedListStack.push(data1);
linkedListStack.push(data2);
linkedListStack.push(data3);
linkedListStack.push(data4);
linkedListStack.push(data5);
linkedListStack.listLinkedListStack();
try {
System.out.println(linkedListStack.pop());
System.out.println(linkedListStack.pop());
System.out.println(linkedListStack.pop());
System.out.println(linkedListStack.pop());
System.out.println(linkedListStack.pop());
}catch (RuntimeException e){
System.out.println(e.getMessage());
}
}
}
class LinkedListStack{
private Data head = new Data(0);
private Data top = head;
public LinkedListStack() {
}
//判断栈空
public boolean isEmpty(){
return top==head;
}
//入栈
public void push(Data value){
Data temp = head;
while(temp.next!=null){
temp = temp.next;
}
temp.next = value;
top = temp.next;
}
//出栈
public int pop(){
if(isEmpty()){
throw new RuntimeException("栈空");
}
Data temp = head;
while(temp.next!=top){
temp = temp.next;
}
int value = top.getData();
top = temp;
temp.next=null;
return value;
}
//遍历栈
public void listLinkedListStack(){
if (isEmpty()){
System.out.println("栈空,无法遍历");
return;
}
Stack<Data> stack = new Stack<>();
Data temp = head.next;
while(temp!=null){
stack.push(temp);
temp = temp.next;
}
while(stack.size()>0){
System.out.println(stack.pop());
}
}
}
class Data{
private int data;
public Data next;
public Data(int data) {
this.data = data;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
@Override
public String toString() {
return "Data{" +
"data=" + data +
'}';
}
}
4.1.4 例题:用栈完成表达式计算(中缀表达式)
思路:
-
通过index来遍历表达式
-
如果是一个数字,入数栈
-
如果是一个符号
3.1 如果符号栈中没有符号,就直接入符号栈
3.2 如果符号栈中有符号
3.2.1 如果当前符号的优先级小于或等于栈中的符号,就从数栈中pop出两个数,从符号栈中pop出一个符号,运算后将结果入数栈,最后将当前符号入符号栈
3.2.2 如果当前符号的优先级大于栈中的符号,就直接入符号栈
-
表达式扫描完毕后,就顺序从数栈和符号栈中pop出相应的数字和符号进行运算
-
最后的数栈中只有一个数字,就是表达式的结果
实现:
public class Calculator {
public static void main(String[] args) {
//表达式
String expression = "9+2*6-2";
//数栈
ArrayStack2 numStack = new ArrayStack2(10);
//符号栈
ArrayStack2 operatorStack = new ArrayStack2(10);
int index = 0;
int num1=0;
int num2=0;
int oper=0;
int res=0;
char ch=' ';
while(true){
//index获取表达式字符
ch = expression.substring(index,index+1).charAt(0);
//如果是一个符号
if(operatorStack.isOperator(ch)){
//判断符号栈中有没有符号
//如果有
if(!operatorStack.isEmpty()){
//判断符号优先级
//如果当前符号的优先级小于或等于栈中的符号
if(operatorStack.priority(ch)<= operatorStack.priority((char)operatorStack.peek())){
//就从数栈中pop出两个数,从符号栈中pop出一个符号,运算后将结果入数栈,最后将当前符号入符号栈
num1 = numStack.pop();
num2 = numStack.pop();
oper = operatorStack.pop();
res = numStack.calculate(num1, num2, oper);
numStack.push(res);
operatorStack.push(ch);
}else{
//如果当前符号的优先级大于栈中的符号,就直接入符号栈
operatorStack.push(ch);
}
}else{
//如果符号栈中没有符号,就直接入符号栈
operatorStack.push(ch);
}
}else{
//如果是一个数字,入数栈
numStack.push(ch-'0');
}
index++;
if(index==expression.length()){
break;
}
}
//表达式扫描完毕后,就顺序从数栈和符号栈中pop出相应的数字和符号进行运算
while(true){
if(operatorStack.isEmpty()){
break;
}
num1 = numStack.pop();
num2 = numStack.pop();
oper = operatorStack.pop();
res = numStack.calculate(num1, num2, oper);
numStack.push(res);
}
//最后的数栈中只有一个数字,就是表达式的结果
System.out.printf("表达式%s的结果是%d",expression,numStack.pop());
}
}
class ArrayStack2{
private int maxSize;
private int stack[];
private int top = -1;
public ArrayStack2(int maxSize){
this.maxSize = maxSize;
stack = new int[maxSize];
}
//获取栈顶信息
public int peek(){
return stack[top];
}
//判断栈空
public boolean isEmpty(){
return top==-1;
}
//判断栈满
public boolean isFull(){
return top==maxSize-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 listArrayStack(){
if(isEmpty()){
System.out.println("栈空,无法遍历");
return;
}
for(int i=top;i>=0;--i){
System.out.printf("stack[%d]=%d\n",i,stack[i]);
}
}
//判断是否为符号
public boolean isOperator(char ch){
return ch=='+'||ch=='-'||ch=='*'||ch=='/';
}
//判断符号优先级
public int priority(char ch){
if(ch=='*'||ch=='/'){
return 1;
}else if(ch=='+'||ch=='-'){
return 0;
}else{
return -1;
}
}
//计算
public int calculate(int num1,int num2,int operator){
int res = 0;
switch (operator){
case '+':
res = num1+num2;
break;
case '-':
res = num2-num1;
break;
case '*':
res = num1*num2;
break;
case '/':
res = num2/num1;
default:
break;
}
return res;
}
}
存在问题:多位数计算的时候结果不正确
//优化解决问题
public static void main(String[] args) {
//表达式
String expression = "90+20*6-2";
//数栈
ArrayStack2 numStack = new ArrayStack2(10);
//符号栈
ArrayStack2 operatorStack = new ArrayStack2(10);
int index = 0;
int num1=0;
int num2=0;
int oper=0;
int res=0;
char ch=' ';
String connChar = "";//用于拼接数字
while(true){
//index获取表达式字符
ch = expression.substring(index,index+1).charAt(0);
//如果是一个符号
if(operatorStack.isOperator(ch)){
//判断符号栈中有没有符号
//如果有
if(!operatorStack.isEmpty()){
//判断符号优先级
//如果当前符号的优先级小于或等于栈中的符号
if(operatorStack.priority(ch)<= operatorStack.priority((char)operatorStack.peek())){
//就从数栈中pop出两个数,从符号栈中pop出一个符号,运算后将结果入数栈,最后将当前符号入符号栈
num1 = numStack.pop();
num2 = numStack.pop();
oper = operatorStack.pop();
res = numStack.calculate(num1, num2, oper);
numStack.push(res);
operatorStack.push(ch);
}else{
//如果当前符号的优先级大于栈中的符号,就直接入符号栈
operatorStack.push(ch);
}
}else{
//如果符号栈中没有符号,就直接入符号栈
operatorStack.push(ch);
}
}else{
/*如果是一个数字,入数栈
numStack.push(ch-'0');*/
//如果是数字,先拼接到connChar
connChar+=ch;
//如果ch已经是最后一个了,直接入数栈
if(index==expression.length()-1){
numStack.push(Integer.valueOf(connChar));
}else{
//如果不是最后一个
//扫描下一个字符
//如果是运算符,则将connChar直接入数栈
if(operatorStack.isOperator(expression.substring(index+1, index+2).charAt(0))){
numStack.push(Integer.valueOf(connChar));
//清空connChar
connChar="";
}
}
}
index++;
if(index==expression.length()){
break;
}
}
//表达式扫描完毕后,就顺序从数栈和符号栈中pop出相应的数字和符号进行运算
while(true){
if(operatorStack.isEmpty()){
break;
}
num1 = numStack.pop();
num2 = numStack.pop();
oper = operatorStack.pop();
res = numStack.calculate(num1, num2, oper);
numStack.push(res);
}
//最后的数栈中只有一个数字,就是表达式的结果
System.out.printf("表达式%s的结果是%d",expression,numStack.pop());
}
4.2 前缀、中缀、后缀表达式
4.2.1 前缀表达式(波兰表达式)
4.2.1.1 前缀表达式介绍
前缀表达式又称波兰表达式,前缀表达式的运算符位于操作数之前
例如:(3+4)*5-6 的前缀表达式为 - * + 3 4 5 6
4.2.1.2 前缀表达式的计算机求值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TBtgHSCu-1638952873284)(C:/Users/80653/AppData/Roaming/Typora/typora-user-images/image-20211205161338736.png)]
4.2.2 中缀表达式
4.2.2.1 中缀表达式介绍
中缀表达式就是最常见的运算表达式,例如:(3+4)*5-6
中缀表达式求值对计算机来说不好操作,往往将中缀表达式转为其他表达式来操作(常常转为后缀表达式)
4.2.3 后缀表达式(逆波兰表达式)
4.2.3.1 后缀表达式介绍
后缀表达式又称为逆波兰表达式,后缀表达式的运算符位于操作数之后
例如:(3+4)*5-6的后缀表达式为 3 4 + 5 * 6 -
4.2.3.2 后缀表达式的计算机求值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ieQPhsF5-1638952873285)(C:/Users/80653/AppData/Roaming/Typora/typora-user-images/image-20211205162350553.png)]
4.2.3.3 逆波兰计算器实现
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 -";
//将suffixExpression放到ArrayList中
List<String> expressionList = getListString(suffixExpression);
System.out.printf("逆波兰表达式\"%s\"的结果是%d",suffixExpression,calculate(expressionList));
}
//将suffixExpression放到ArrayList中
public static List<String> getListString(String suffixExpression){
//按空格分割,放入String数组
String[] splits = suffixExpression.split(" ");
List<String> expressionList = new ArrayList<>();
//将String数组中的元素依次放入expressionList
for(String element:splits){
expressionList.add(element);
}
return expressionList;
}
//传入expressionList,结合栈计算结果
public static int calculate(List<String> expressionList){
Stack<String> stack = new Stack<>();
for(String item:expressionList){
if(item.matches("\\d+")){
stack.push(item);
}else{
int num2= Integer.valueOf(stack.pop());
int num1= Integer.valueOf(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{
res = num1 / num2;
}
stack.push(Integer.toString(res));
}
}
return Integer.valueOf(stack.pop());
}
}
4.2.4 中缀转后缀表达式
思路:
-
初始化两个栈,一个存放符号的符号栈s1,一个存储中间结果的临时栈s2
-
从左至右扫描表达式
-
遇到操作数,直接压入s2
-
遇到运算符时,比较其与s1栈顶运算符的优先级;
4.1 如果s1为空,或者s1栈顶为‘(’,则直接入符号栈s1
4.2 否则,如果运算符的优先级大于栈顶运算符的优先级,也直接压入符号栈s1
4.3 否则,将s1栈顶的运算符弹出并压入到s2中,然后再回到 4.1 与s1中新的栈顶运算符比较
-
遇到括号时
5.1 如果是‘(’,则直接压入s1
5.2 如果是’)’,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号,最后需要丢弃这一对括号
-
重复2到5步,直到表达式扫描完毕
-
将s1中的符号依次弹出压入到s2
-
依次弹出s2中的元素并输出,结果的逆序就是中缀表达式对应的后缀表达式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UAkPTSq3-1638952873286)(C:/Users/80653/AppData/Roaming/Typora/typora-user-images/image-20211207154559720.png)]
实现:
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class PolandNotation {
public static void main(String[] args) {
String infixExpression = "1+((2+3)*4)-5";
//将infixExpression放入到list方便后面的操作
List<String> list = new ArrayList<>();
list = toInfixExpressionList(infixExpression);
System.out.printf("中缀表达式%s对应的后缀表达式是:%s\n",infixExpression,parseSuffixExpressionList(list));
System.out.printf("表达式%s的结果是:%d\n",infixExpression,calculate(parseSuffixExpressionList(list)));
}
//将中缀表达式转为后缀表达式
public static List<String> parseSuffixExpressionList(List<String> list){
Stack<String> s1 = new Stack<>();
List<String> s2 = new ArrayList<>();//s2没有出栈操作,而且s2栈的结果需要逆序,所以使用List更方便
for(String str : list){
//如果遇到操作数,直接加入s2
if (str.matches("\\d+")){
s2.add(str);
} else if(str.equals("(")){//如果遇到"(",直接压入s1
s1.push(str);
} else if(str.equals(")")){//如果是')',则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号,最后需要丢弃这一对括号
while(!s1.peek().equals("(")){
s2.add(s1.pop());
}
s1.pop();
} else {
while(s1.size()!=0 && getPriority(s1.peek()) >= getPriority(str)){
s2.add(s1.pop());
}
s1.push(str);
}
}
while(s1.size()!=0){
s2.add(s1.pop());
}
return s2;
}
//判断符号优先级
public static int getPriority(String operator){
int res = 0;
switch (operator){
case "+":
case "-":
res = 1;
break;
case "*":
case "/":
res = 2;
break;
default:
break;
}
return res;
}
//将infixExpression放入到list
public static List<String> toInfixExpressionList(String infixExpression){
List<String> list = new ArrayList<>();//用于存放结果
int index = 0;//用于遍历infixExpression
char ch = ' ';//用于获取当前字符
String temp = "";//用于拼接多位数
int length = infixExpression.length();
while(index<length){
ch = infixExpression.charAt(index);
if(ch<'0'||ch>'9'){
list.add(ch+"");
index++;
}else{
temp = "";
while(ch>='0'&&ch<='9'){
temp+=ch;
index++;
if(index<length) {
ch = infixExpression.charAt(index);
}else{
break;
}
}
list.add(temp);
}
}
return list;
}
//将suffixExpression放到ArrayList中
public static List<String> getListString(String suffixExpression){
//按空格分割,放入String数组
String[] splits = suffixExpression.split(" ");
List<String> expressionList = new ArrayList<>();
//将String数组中的元素依次放入expressionList
for(String element:splits){
expressionList.add(element);
}
return expressionList;
}
//传入expressionList,结合栈计算结果
public static int calculate(List<String> expressionList){
Stack<String> stack = new Stack<>();
for(String item:expressionList){
if(item.matches("\\d+")){
stack.push(item);
}else{
int num2= Integer.valueOf(stack.pop());
int num1= Integer.valueOf(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{
res = num1 / num2;
}
stack.push(Integer.toString(res));
}
}
return Integer.valueOf(stack.pop());
}
}