提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
数据结构和算法-第五章栈
提示:以下是本篇文章正文内容,下面案例可供参考
一、栈的介绍
1. 栈的介绍
2. 栈的应用场景
二、数组模拟栈
1. 数组模拟栈分析
2.数组模拟栈代码
public class ArrayStackDemo {
public static void main(String[] args) {
//测试ArrayStack
//先创建一个对象
ArrayStack arrayStack=new ArrayStack(4);
//扫描器
Scanner sc=new Scanner(System.in);
//控制是否退出菜单
boolean loop=true;
String key="";
while(loop){
System.out.println("show:表示显示栈");
System.out.println("exit:退出程序");
System.out.println("push:表示添加数据到栈(入栈)");
System.out.println("pop:表示从栈取出数据(出栈)");
System.out.println("请输入你的选择");
key=sc.next();
switch(key){
case"show":
arrayStack.list();
break;
case"push":
System.out.println("请输入一个数");
arrayStack.push(sc.nextInt());
break;
case"pop":
try{
System.out.printf("出栈的数据为%d\n",arrayStack.pop());
}catch(Exception e){
System.out.println(e.getMessage());
}
break;
case"exit":
//关闭输入流
sc.close();
//退出循环
loop=false;
break;
default:
break;
}
}
System.out.println("程序退出~");
}
}
//定义一个ArrayStack表示栈
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("栈空,没有数据~");
}
//因为要把栈顶取出,同时要把栈顶-1,所以需要一个变量来保存取出的值
int value=stack[top];
top--;
return value;
}
//遍历栈(出栈是从栈顶出,从栈顶往下遍历)
public void list(){
//判断栈是否为空
if(isEmpty()){
System.out.println("栈为空,没有数据~");
}
//需要从栈顶开始显示数据
for(int i=top;i>=0;i--){
System.out.printf("stack[%d]=%d\n",i,stack[i]);
}
}
}
```c
3.使用单链表模拟栈代码及分析
- 使用单链表来模拟栈:注意我们在入栈的时候采用的是链表的头插法
- 即就是入栈时,将该节点插入到(头节点)和(头节点的下一个节点)之间,遍历的时候从头节点开始遍历,仿照栈先进后出的特点
public class Main {
public static void main(String[] args) {
//测试SingleLinkedListStack
//先创建一个对象
SingleLinkedListStack arrayStack=new SingleLinkedListStack();
//扫描器
Scanner sc=new Scanner(System.in);
//控制是否退出菜单
boolean loop=true;
String key="";
while(loop){
System.out.println("show:表示显示栈");
System.out.println("exit:退出程序");
System.out.println("push:表示添加数据到栈(入栈)");
System.out.println("pop:表示从栈取出数据(出栈)");
System.out.println("请输入你的选择");
key=sc.next();
switch(key){
case"show":
arrayStack.list();
break;
case"push":
System.out.println("请输入一个数");
Node newNode=new Node(sc.nextInt());
arrayStack.push(newNode);
break;
case"pop":
arrayStack.pop();
break;
case"exit":
//关闭输入流
sc.close();
//退出循环
loop=false;
break;
default:
break;
}
}
System.out.println("程序退出~");
}
}
//构建链表的节点有了,接下来就是构建链表了——SingleLinkedListStack类,注意我们在入栈的时候采用的是链表的头插法
class SingleLinkedListStack{
//定义一个头指针,代表栈顶
private Node top=new Node(-1);
//判断栈是否为空
public boolean isEmpty(){
return top.getNext()==null;
}
//入栈——push--采用的是头插法
public void push(Node node){
//插入第一个节点
if(top.getNext()==null){
top.setNext(node);
return;
}
//定义一个变量使其指向top的下一个节点,将每个节点插入到头节点和其后一位节点之间
Node temp=top.getNext();
top.setNext(node);
node.setNext(temp);
}
//出栈
public void pop(){
if(top.getNext()==null){
System.out.println("栈空,没有数据~");
}
System.out.println("节点为:"+top.getNext().getValue());
//将top后移
top=top.getNext();
}
//遍历栈
public void list(){
if(isEmpty()){
System.out.println("栈为空~");
return;
}
//因为头节点不能动,所以我们需要一个辅助变量来遍历
Node temp=top.getNext();
while(true){
//判断链表是否到最后
if(temp==null){
return;
}
//输出节点的信息,并将辅助变量后移
System.out.println("节点为:"+temp);
temp=temp.getNext();
}
}
}
//定义链表节点
class Node{
private int value;//链表值
private Node next;//指向下一个节点
//构造器
public Node(int value){
this.value=value;
}
//set和get方法
public int getValue(){
return value;
}
public void setValue(int value){
this.value=value;
}
public Node getNext(){
return next;
}
public void setNext(Node next){
this.next=next;
}
//重写toString方法
@Override
public String toString() {
return "HeroNode值为:"+value;
}
}
```c
4. 数组模拟栈小练
https://leetcode-cn.com/problems/valid-parentheses/submissions/
三、栈实现综合计算器(中缀表达式)
1. 栈实现综合计算器分析
栈实现综合计算器: 数栈(numStack)存放数 符号栈(operStack)存放运算符
(1)通过一个index值(索引),来遍历我们的表达式
(2)如果发现这是一个数字,就直接加入数栈numStack中
(3)如果发现是一个符号
1.1,如果当前符号栈operStack为空,就直接入栈
2.2,如果符号栈operStack有操作符就进行比较
* * 1,如果当前操作符的优先级大于栈中操作符operStack,就直接加入到符号栈operStack
* * 2,如果当前操作符的优先级小于或者等于栈中的操作符
就需要从数栈中numStack中pop出两个数,再从符号栈operStack中pop出一个符号进行运算,将得到的结果入数栈,然后将当前的操作符加入到符号栈operStack
(4)当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并计算
(5)最后在数栈numStack中只有一个数字,就是表达式的结果
2.栈实现综合计算器代码
public class Calculator {
public static void main(String[] args) {
String expression = "70*8-60+20"; // 18//如何处理多位数的问题?
//创建两个栈,数栈和符号栈
ArrayStack2 numStack=new ArrayStack2(10);
ArrayStack2 operStack=new ArrayStack2(10);
int index=0;//用于扫描的索引
int num1=0; int num2=0;//弹栈出的数据num1和num2
int oper=0;//用于接收操作符的oper
int res=0;//运算得到的结果
char ch=' ';//将每次扫描得到的char保存到ch
String keepNum="";//用于拼接多位数
//开始while循环的扫描expression
while(true){
//依次得到exception的每一个字符
ch=expression.substring(index,index+1).charAt(0);//把字符串转为字符
//判断ch时字符还是数字
//是一个字符
if(operStack.isOper(ch)){
if(!operStack.isEmpty()){
/**
* 如果当前符号栈不为空
* 1,如果当前操作符的优先级大于栈中操作符operStack,就直接加入到符号栈operStack
* 2,如果当前操作符的优先级小于或者等于栈中的操作符
* 就需要从数栈中numStack中pop出两个数,再从符号栈operStack中pop出一个符号进行运算,将得到的结果入数栈
* 然后将当前的操作符加入到符号栈operStack
*/
if(operStack.priority(ch)<=operStack.priority(operStack.peek())){
//从数栈numStack中取出两个数
num1=numStack.pop();
num2=numStack.pop();
//再从符号栈中取出一个符号
oper=operStack.pop();
//进行运算
res=numStack.cal(num1,num2,oper);
//把运算得到的结果加入到数栈中
numStack.push(res);
//然后将当前的操作符加入到符号栈中
operStack.push(ch);
}else{
//如果当前的操作符的优先级大于栈中操作符operStack,就直接加入到符号栈operStack
operStack.push(ch);
}
}else{
//如果当前符号栈为空,就直接入栈
operStack.push(ch);
}
}else{
//如果是数,就直接入数栈
//扫描到是字符'1',和数字1相差48
//numStack.push(ch-48);
/**
* 处理多位数
* 1,在处理多位数时,不能发现是一个数就立即入栈,因为它可能是多位数
* 2,在处理数时,需要向expression的表达式的index后再看一位,如果是数就进行扫描,如果是符号才入栈
* 3,因此我们需要定义一个变量字符串,用于拼接
*/
//先拼接ch
keepNum+=ch;
//如果ch已经是expression的最后一位,就直接入栈
if(index==expression.length()-1){
numStack.push(Integer.parseInt(keepNum));
}else {
//判断下一个字符是不是数字,如果是数字,就继续扫描,如果是运算符,就入数栈numStack
//注意是看后一位,不是将index++
if (operStack.isOper(expression.substring(index + 1, index + 2).charAt(0))) {
//如果后一位是运算符,就直接入数栈,因为此时的keepNum是一个字符串要将它转为一个int
numStack.push(Integer.parseInt(keepNum));
//注意这个字符串已用完,后面还要使用keepNum,所以要将keepNum清空
keepNum = "";
}
}
}
//让index加1,并判断是否扫描到exception最后
index++;
if(index>=expression.length()){
break;
}
}
//(4)当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并计算
//(5)最后在数栈numStack中只有一个数字,就是表达式的结果
while(true){
//如果符号栈为空,则计算到最后的结果,数栈中只有一个数字(结果)
if(operStack.isEmpty()){
break;
}
num1=numStack.pop();
num2=numStack.pop();
oper=operStack.pop();
res=numStack.cal(num1,num2,oper);
numStack.push(res);//再将得到的结果入栈
}
System.out.printf("表达式%s=%d",expression,numStack.pop());
}
}
//定义一个 ArrayStack2 表示栈
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 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("栈为空~");
}
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;//res用于存放计算的结果
switch(oper){
case'+':
res=num2+num1;
break;
case'-':
res=num2-num1;
break;
case'*':
res=num2*num1;
break;
case'/':
res=num2/num1;
break;
default:
break;
}
return res;
}
}
```c
3. 栈实现综合计算器小练
https://leetcode-cn.com/problems/calculator-lcci/submissions/
四、前缀,中缀,后缀表达式规则
1. 前缀表达式的计算机求值
2. 中缀表达式
中缀表达式就是常见的运算表达式(一般要转为后缀表达式)
3. 后缀表达式
1,后缀表达式
2,后缀表达式计算机求值
五、逆波兰计算器
1. 逆波兰计算器分析(即为后缀表达式的计算机求值)
思路:
1,先将3 4 + 5 × 6 -放入到ArrayList中
2,将ArrayList传递给一个方法,遍历ArrayList配合栈完成计算
2.逆波兰计算器代码
//1.写一个逆波兰表达式,依次将数据和运算符放入到ArrayList中
public static List<String> getListString(String suffixExpression) {
//将suffixExpression按空格分割
String[] split = suffixExpression.split(" ");
//创建集合
List<String> list = new ArrayList<String>();
//将分割出的数据和运算符放入到ArrayList中
for (String ele : split) {
list.add(ele);
}
return list;
}
/**
2,完成对逆波兰表达式的计算
(1),遍历list集合,如果是数就入栈
(2),如果是运算符就从栈中弹出两个数,进行运算,然后将结果入栈
(3),最后留在栈中的就是运算结果
*/
public static int calculate(List<String> list) {
//创建栈,只需要一个栈即可
Stack<String> stack = new Stack<String>();
//遍历list集合 /item一件物品,元素/
for (String item : list) {
//如果是数,就入栈(使用正则表达式)
if (item.matches("\\d+")) {
//匹配的是多位数,就入栈
stack.push(item);
} else {
//如果是运算符,就从栈中弹出两个数,计算它们的值,然后再将结果入栈
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("运算符错误~");
}
//然后将运算得到的值入栈
stack.push("" + res);
}
}
//最后留在stack中的是运算结果
return Integer.parseInt(stack.pop());
}
```c
3. 力扣题解
public int evalRPN(String[] tokens) {
/**
1,遍历字符串数组,如果是数就加入到栈中
2,如果是运算符,就弹出栈顶的两个数,用运算符对它们做相应的运算,并将结果入栈
3,最后留在栈中的就是运算结果
*/
//创建栈
Stack<Integer> stack=new Stack<Integer>();
//遍历字符串数组,如果是数就加入到栈中
for(String item:tokens){
switch(item){
case "+":
stack.push(stack.pop()+stack.pop());
break;
case "-":
//2,如果是运算符,就弹出栈顶的两个数,用运算符对它们做相应的运算,并将结果入栈
int a=stack.pop();
stack.push(stack.pop()-a);
break;
case "*":
stack.push(stack.pop()*stack.pop());
break;
case "/":
//2,如果是运算符,就弹出栈顶的两个数,用运算符对它们做相应的运算,并将结果入栈
int b=stack.pop();
stack.push(stack.pop()/b);
break;
default:
//如果不是符号,就是数直接入栈
stack.push(Integer.parseInt(item));
break;
}
}
// 3,最后留在栈中的就是运算结果
return stack.pop();
}
```c
4. 逆波兰计算器小练
https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/
六、中缀表达式转后缀表达式
1. 中缀表达式转后缀表达式分析
2.中缀表达式转后缀表达式代码
//由于对字符串直接扫描不方便
//1,方法:将中缀表达式转成对应的List
public static List<String> toInfixException(String s) {
//定义一个list,存放中缀表达式,对应的内容
List<String> list = new ArrayList<String>();
//这是一个指针,用于遍历中缀表达式字符串
int i = 0;
//对多位数的拼接
String str;
//每遍历到一个字符就放到c
char c;
do {
//如果c是一个非数字,就加入到list
if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57) {
list.add("" + c);
i++;//i需要后移
} else {
//如果是一个数,需要考虑多位数,将其进行拼接
str = "";//先将str置为空,'0'->[48]//'9'->[57]
while (i < s.length() && (c = s.charAt(i)) >= 48 && (c = s.charAt(i)) <= 57) {
str += c;//拼接
i++;
}
//将拼接后的多位数加入到list
list.add(str);
}
} while (i < s.length());
return list;//返回链表
}
//2. 将得到的中缀表达式对应的 List => 后缀表达式对应的 List
// 即ArrayList [1,+,(,(,2,+,3,),*,4,),-,5] =》 ArrayList [1,2,3,+,4,*,+,5,–]
public static List<String> parseSuffixExpressionList(List<String> ls) {
//1) 初始化两个栈:运算符栈 s1 和储存中间结果的栈 s2;
//符号栈s1
Stack<String> s1 = new Stack<String>();
//因为s2这个栈,中间没有使用pop操作,并且最后还需要逆序输出,因此比较麻烦,所以我们不用Stack<String>而是用List<String>
List<String> s2 = new ArrayList<String>();
//遍历中缀表达式
for (String item : ls) {
if (item.matches("\\d+")) {
//3) 遇到操作数时,将其压 s2;
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();//将(括号移除s1,消除小括号
} else {
//4) 遇到运算符时,比较其与 s1 栈顶运算符的优先级: 缺少一个比较优先级的算法
//当 item 的优先级小于等于 s1 栈顶运算符, 将 s1 栈顶的运算符弹出并加入到 s2 中,再次转到(4.1) 与 s1 中新的栈顶运算符相比较
while (s1.size()!=0 && getValue(item) <= getValue(s1.peek())) {
s2.add(s1.pop());
}
//1.如果 s1 为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
// * 2.否则,若优先级比栈顶运算符的高,也将运算符压入 s1;
s1.push(item);
}
}
// 7) 将 s1 中剩余的运算符依次弹出并压入 s2
while (s1.size() != 0) {
s2.add(s1.pop());
}
//注意因为是存放到 List有序的, 因此按顺序输出就是对应的后缀表达式对应的 List
return s2;
}
//写一个方法getValue,返回对应的优先级数字getValue
public static int getValue(String operation) {
int result = 0;
switch (operation) {
case "+":
result = 1;
break;
case "-":
result = 1;
break;
case "*":
result = 2;
break;
case "/":
result = 2;
break;
default:
//System.out.println("不存在该运算符");
break;
}
return result;
}
```c
3. 中缀表达式转后缀表达式小练
https://leetcode-cn.com/problems/basic-calculator-ii/
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。