一:栈的介绍
- 栈的英文为(stack)
- 栈是一种先入后出的有序列表
- 栈是限制线性表中的元素的插入和删除只能在线性表中的一端进行的一种特殊线性表,允许插入和删除的一端,为变化的一端,称为栈顶,另一端为固定的一端,称为栈底.
- 根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素正好相反,最后放入的元素最先删除,最先放入的元素最后删除.
二:栈的应用场景
- 子程序的调用:在跳往子程序前,会先将下一个指令的地址存到堆栈中,直到子程序执行完后将地址取出,以便恢复到原来的程序中
- 处理递归调用:和子程序的调用类似,只是出了储存下一个指令的地址外,也将参数.区域变量等数据存入堆栈中
- 表达式的转换[中缀表达式转后缀表达式]与求值(实际解决)
- 二叉树的遍历
- 图形的深度优先搜索法
三:快速入门
- 用数组模拟栈的使用,由于栈是一种有序列表,所以我们可以使用数组的结构来存储栈的数据内容,下面我们就用数组模拟栈的出栈,入栈的操作
- 思路分析
- 使用数组来模拟栈
- 定义一个top来表示栈顶,初始化为-1
- 入栈的操作,当有数据加入到栈时,top++,stack[top] =data
- 出栈的操作,从栈顶取出数据 int value = stack[top];top-- ,return value
代码实现:
package com.qiu.stack;
import java.util.Scanner;
public class ArrayStackDemo {
public static void main(String[] args) {
//测试一下这个arrayStack是否正确
ArrayStack stack = 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("pop:表示弹出栈");
System.out.println("push:表示压入栈");
System.out.println("请输入你的选择:");
key = scanner.next();
switch (key){
case "show":
stack.list();
break;
case "exit":
scanner.close();
loop = false;
break;
case "pop":
try{
int res = stack.pop();
System.out.printf("出栈的数据为%d\n",res);
}catch (Exception e){
System.out.println(e.getMessage());
}
stack.pop();
break;
case "push":
System.out.println("请输入一个数");
int value =scanner.nextInt();
stack.push(value);
break;
default:
break;
}
}
System.out.println("程序正常退出了!");
}
}
//定义一个类,表示栈结构
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;
}
//出栈
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]);
}
}
}
代码运行演示:
假如说用单3链表去模拟栈呢?
首先我们先要分析下思路:
使用链表模拟栈
1.遍历变量显示,和单链显示一样
2.进栈和单链表的添加一样
3.出栈则不同,按照栈的结构特点,出栈应该是出的最后一个元素.
代码展示:
package com.qiu.stack;
import java.util.Scanner;
public class LinkedListStackDemo {
public static void main(String[] args) {
//使用链表去模拟栈
LinkedListStack stack = new LinkedListStack();
LinkStackNode node = new LinkStackNode(1);
LinkStackNode node1 = new LinkStackNode(2);
LinkStackNode node2 = new LinkStackNode(3);
LinkStackNode node3 = new LinkStackNode(4);
stack.push(node);
stack.push(node1);
stack.push(node2);
stack.push(node3);
stack.show();
System.out.println("测试弹出元素--------");
stack.pop();
}
}
class LinkedListStack{
private LinkStackNode head =new LinkStackNode(0);
//这里我们编写一些方法
//显示show方法
public void show(){
LinkStackNode temp =head.getNext();
if (head.getNext()== null){
System.out.println("该栈为空");
return;
}
while (true){
if (temp == null){
//遍历栈完毕
break;
}
System.out.println(temp.getNo());
temp = temp.getNext();
}
}
//定义添加的方法
public void push(LinkStackNode stackNode){
LinkStackNode temp = head;
boolean flag = false;
while(true){
if (temp.getNext() == null){
//遍历到了最后
break;
}
//添加还会涉及到重复的问题
if (temp.getNext().getNo() == stackNode.getNo()){
flag = true;
break;
}
temp = temp.getNext();
}
if (flag){
System.out.printf("待添加的数据与栈中的数据编号%d有重复,请重新输入!",stackNode.getNo());
return;
}
temp.setNext(stackNode);
}
//定义pop方法,就是弹出最末尾的数据,相对来说就是每次都取出最末尾的数据
//弹出数据的时候我们要考虑到栈中是否有数据
public int pop(){
if (head.getNext() == null){
System.out.println("该栈中没有数据!");
return 0;
}
LinkStackNode temp = head.getNext();
while(true){
if (temp.getNext() == null){
break;
}
temp =temp.getNext();
}
int value = temp.getNo();
System.out.println("栈中取出的数据为:"+temp);
return value;
}
}
//定义链表的结构
class LinkStackNode{
private int no;
private LinkStackNode next;
public LinkStackNode(int no) {
this.no=no ;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public LinkStackNode getNext() {
return next;
}
public void setNext(LinkStackNode next) {
this.next = next;
}
@Override
public String toString() {
return "LinkStackNode{" +
"no=" + no +
'}';
}
}
代码结果展示:
实例三:栈实现综合计算机的运算
思路分析:
- 首先通过一个index值(索引),我们用这个index进行一个表达式的遍历
- 我们要准备两个栈,第一个是数栈,第二个是符号栈
- 遍历表达式的时候,如果说遍历到的是数字的话,则入数栈
- 如果说遍历到的是符号号的话,需要分两种情况.
- 第一种:符号栈中没有符号的话,则遍历到的这个符号则直接入栈
- 第二种:如果说符号栈中有符号的话,则又需要判断情况
情况一:如果说我们遍历到的这个符号与符号栈中的符号相比,遍历的符号比栈中的符号优先级低或者相等的情况下,则需要从数栈中弹出两个数,从符号栈中弹出一个符号.并进行计算,将得到的结果放入数栈中.然后再将当前的符号入符号栈.
情况二:如果说我们遍历到的这个符号与符号栈中的符号相比,遍历的符号比栈中的符号优先级高的话,则直接入符号栈.
5. 当表达式扫描完毕之后,就顺序的从数栈和符号栈中取出相应的数组和符号进行运算,最后留着数栈中的值就是最终结果.
验证表达式:3+2*6-2
步骤:
- 首先将3放入数栈中
- 遍历到了符号+,所以将+入符号栈中
- 扫描到了数字2,直接放入符号栈中
- 扫描到了符号
*
这个时候符号栈中有一个符号+
,并且*
比+
的优先级要高.所以*
直接入符号栈, - 再往下面扫描发现是一个6,直接放入数栈中
- 再进行往下扫描,发现是一个
-
这个时候又需要对比符号栈,然而这个-
比符号栈中的*
优先级要低.
所以,我们需要从数栈中pop两个数,分别是6,2然后符号栈中的*
也pop出来,并进行运算.结果为12.放入数栈中,再将符号-
放入符号栈 - 扫描后发现是一个2,直接入数栈.
- 最终表达式扫描完毕之后,按顺序弹出2,12 .符号栈中pop出一个
-
,并进行运算.得到数字10(这里运算有一个顺序就是一定是栈下面的数减去栈上面的数),接着在弹出10和3,和符号栈中的+
,最终运算后
得到13.最终数栈剩余10和13,但是数栈中的最后一个数字就是13.这也是最终的运算结果.
代码演示:
package com.qiu.stack;
public class Calculator {
public static void main(String[] args) {
//根据前面的思路,完成表达式的运算
String expression = "3+2*9-2";
ArrayStack2 numStack = new ArrayStack2(10);
ArrayStack2 operStack = new ArrayStack2(10);
int index = 0;//用于扫描
int num1 = 0;
int num2 = 0;
char oper = 0;
int res = 0;
char ch = ' ';//将每次扫描得到的char保存到ch中
//开始用while循环扫描expression
while(true){
//依次得到expression中的每一个字符
ch = expression.substring(index,index+1).charAt(0);
//判断ch是什么,做成相应的处理
if (operStack.isOper(ch)){
//如果是运算符,判断当前的符号栈是否为空
if (!operStack.isEmpty()){
//如果当前的符号栈中的符号不为空,则需要进行判断
if (operStack.priority(ch)<= operStack.priority(operStack.peek())){
//需要查看当前符号栈顶的操作符
num1 = numStack.pop();
num2 = numStack.pop();
oper = (char) operStack.pop();
res = numStack.cal(num1,num2,oper);
//运算完之后运算结果,入数栈,再把当前的结果入符号栈
numStack.push(res);
operStack.push(ch);
}else{
//直接入栈
operStack.push(ch);
}
}else {//
//直接入栈
operStack.push(ch);
}
}else {
//如果扫描的是数的话,直接入数栈
numStack.push(ch-48);
}
//让index+1,并判断是否扫描到了expression的最后
index++;
if (index>=expression.length()){
break;
}
}
//表达式扫描完毕之后,再进行取
while(true){
//如果符号栈为空,则计算最后的结果,数栈只有一和结果
if (operStack.isEmpty()){
break;
}
num1 = numStack.pop();
num2 = numStack.pop();
oper = (char) operStack.pop();
res = numStack.cal(num1,num2,oper);
numStack.push(res);//入栈
}
//将数栈中最后的一个数pop出来就是结果
int res2 = numStack.pop();
System.out.printf("表达式%s = %d",expression,res2);
}
}
//先创建一个栈,直接使用前面创建好的一个数组栈
//定义一个类,表示栈结构
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];//初始化数组,才能放数据
}
//栈满
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;
}
//出栈
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,char 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;
}
public int peek(){
return stack[top];
}
}
代码运行结果:
看起来我们觉得好像是没有问题的:但是有一个很细节上面的问题
加入数,是多位数呢?
运行如下:
结果明显是错的.这是为什么呢?
分析思路:
- 当处理多位数的时候,不能发信是一个数就立即入栈,因为他可能是多位数
- 在处理数时,需要向expression的表达式的index后多看一位,如果是数就进行扫描,如果是符号才入栈
- 因此我们需要定义一个字符串变量,进行拼接多位数
添加代码:
String keepNum = ""; //先定义一个字符变量,用来存储这个多位数
方法里面添加代码:
keepNum+=ch;//用于拼接多位数
//如果说ch已经是expression最后一位则直接入栈
if (index == expression.length()-1){
numStack.push(Integer.parseInt(keepNum));
}else{
//判断下一个字符是不是数字,如果是数字就继续扫描
//只是往后面看一位,不改变结构
if (operStack.isOper(expression.substring(index+1,index+2).charAt(0))){
numStack.push(Integer.parseInt(keepNum));
//重要的一点
keepNum = "";
}
多位数代码运行结果:
正确!