栈
问题引入
一个计算器的简单实现,计算机怎么理解算式的,对于计算机而言,他接受到的就是一个字符串,而且不是两个数之间,而是一串算式计算
你就得考虑拆分数字和运算符,你还得考虑计算优先级等等
引出栈的介绍
什么是栈
1)栈的英文为(stack)
2)栈是一个先入后出(FILO-First In Last Out)的有序列表。
3)栈(stack)是 限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端, 称为栈底(Bottom)。
4)根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除
应用场景
- 子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。类似我们的retrun语句
- 处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
- 表达式的转换**(中缀表达式转后缀表达式)**与求值(实际解决)。
- 二叉树的遍历。
- 图形的深度优先(depth- first)搜索法。
数组模拟栈的思路
1.定义一个top来表示我们的栈顶,初始化为-1
2.当有数据加入到栈时,top++,数据stack[top] = data;
3.出栈操作,int value = stack[top]; top–; return value;
栈满的情况:top = maxSize -1;
栈空的情况:top = -1;
代码实现
/**
* @author 王庆华
* @version 1.0
* @date 2020/12/15 20:09
* @Description TODO
* @pojectname 算法代码
*/
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("push:入栈");
System.out.println("pop:出栈");
System.out.println("输入你的选择");
key = scanner.next();
switch (key){
case "show":{
stack.list();
break;
}
case "push":{
System.out.println("请输入你入栈的数值");
int value = scanner.nextInt();
stack.push(value);
break;
}
case "pop":{
try {
int reslut = stack.pop();
System.out.println("出栈的数据是"+reslut);
}catch (Exception e){
e.getMessage();
}
break;
}
case "exit":{
scanner.close();
loop = false;
break;
}
}
}
System.out.println("程序退出了");
}
}
//顶一个ArrayStack 表示我们的栈
class ArrayStack{
private int maxSize;//栈的大小
private int[] stack;//数组模拟栈,数据存储在该数组中
private int top = -1; //表示栈顶 初始化为-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;
}
//入栈
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("栈空,没有数据");
return;
}
for (int i = top;i>=0;i--){
System.out.println("索引是"+i+"值为"+stack[i]);
}
}
}
单链表实现栈
package com.wang.stack;
import java.util.Scanner;
import java.util.Stack;
/**
* @author 王庆华
* @version 1.0
* @date 2020/12/15 20:29
* @Description TODO
* @pojectname 算法代码
*/
public class LinkedStackDemo {
public static void main(String[] args) {
LinkedListStack stack = new LinkedListStack();
String key = "";
boolean loop = true;
Scanner scanner = new Scanner(System.in);
while (loop) {
System.out.println("show: 表示显示栈");
System.out.println("push: 表示添加数据到栈(入栈)");
System.out.println("pop: 表示从栈取出数据(出栈)");
System.out.println("length: 表示栈有多少个数据");
System.out.println("top: 表示栈顶的值");
System.out.println("exit: 退出程序");
System.out.println("请输入你的选择");
key = scanner.next();
switch (key) {
case "show":
stack.list();
break;
case "push":
System.out.println("请输入一个数");
int value = scanner.nextInt();
stack.push(value);
break;
case "pop":
try {
Object res = stack.pop();
System.out.println("出栈的数据是:" + res);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case "length":
int length = stack.getLength();
System.out.println("栈有" + length + "个数据");
break;
case "top":
try {
Object top = stack.top();
System.out.println("栈顶为:" + top);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case "exit":
scanner.close();
loop = false;
break;
default:
break;
}
}
System.out.println("程序退出");
}
}
class LinkedListStack {
ListStack head = new ListStack(null);//初始化节点
//判断空
public boolean isEmpty() {
return head.getData() == null;
}
//入栈
public void push(Object value) {
if (head.getData() == null) {
head.setData(value);
} else {
ListStack newStack = new ListStack(value);//新增节点
newStack.setNext(head);//新增节点的下一个节点是上一个节点
head = newStack;//头结点变回开始位置
}
}
//出栈
public Object pop() {
Object data = null;
if (isEmpty()) {
throw new RuntimeException("栈空");
}
data = head.getData();
//原来的节点出去了,现在的头结点是原来的下一个节点
head = head.getNext();
return data;
}
//展示我们的链表实现的栈
public void list() {
if (isEmpty()) {
System.out.println("栈空");
}
ListStack temp = head;//辅助指针
while (temp!= null) {//还有元素
System.out.println(temp.getData());
temp = temp.getNext();//后移以为继续输出
}
}
//取栈顶,但是不删除
public Object top() {
Object data = null;
if (isEmpty()) {
throw new RuntimeException("栈空");
}
data = head.getData();
return data;
}
//获取栈的长度
public int getLength() {
int count = 0;
ListStack temp = head;
if (isEmpty() || temp.getData() == null) {
return 0;
} else {
while (temp != null) {
count++;
temp = temp.getNext();
}
}
return count;
}
}
class ListStack {
private Object data;//存储数据
private ListStack next;//指向下一个节点
public ListStack(Object data) {
this.data = data;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public ListStack getNext() {
return next;
}
public void setNext(ListStack next) {
this.next = next;
}
}
问题解决 使用栈完成计算表达式结果
思路分析:
数栈:存放数据 numStack
符号栈:存放符号位 operStack
1.通过一个index值(可以认为是索引)来遍历表达式
2.如果我们发现index扫描到的位置是数字,就直接入数栈
3.如果发现扫描到的是一个符号,分如下情况来解决
如果发现当前符号栈为空,就直接入栈
如果符号栈不为空(有操作符),就进行比较,如果当前的操作符的优先级小于或等于栈中的操作符,就需要从数栈中pop出两个数,再从符号栈中pop出一个符号进行运算,将得到的结果在入数栈,然后将当前的操作符入符号栈
如果符号栈不为空(有操作符)且当前扫描到的运算优先级大于栈中的操作符,直接入符号栈
4.扫描完毕过后,就顺序的从数栈和符号栈中pop出相应的数和符号并运算
5.最后在数栈中只有一个数字,就表示结果
代码 中缀表达式
package com.wang.stack;
/**
* @author 王庆华
* @version 1.0
* @date 2020/12/18 21:41
* @Description TODO
* @pojectname 算法代码 计算器
*/
public class Calculator {
public static void main(String[] args) {
//构建表达式
String expression = "3+2*6-2";
//创建两个栈一个数栈,一个符号栈
ArrayStack2 numStack = new ArrayStack2(10);
ArrayStack2 operStack = new ArrayStack2(10);
//定义需要的相关变量
int index = 0; //用于帮助我们扫描
int num1 = 0;
int num2 = 0;
int oper = 0; //扫描到的操作符
int res = 0; //结果
char ch = ' '; //扫描到的字符
//开始循环扫描
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.pick())){
num1 = numStack.pop();
num2 = numStack.pop();
oper = 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);//ch是作为字符传入的 1 对应的是49 3 对应的是51,相差48,我们要减去48
}
//idnex+1,判断是否到我们字符串表达式的最后
index++;
if (index>= expression.length()){
break;
}
}
//遍历完了我们的字符串表达式
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.println("表达式结果是:"+numStack.pop());
}
}
//拿了我们以前的数组实现栈的代码
//顶一个ArrayStack 表示我们的栈
//但是我们要增加一些功能 如判断优先级等等
class ArrayStack2{
private int maxSize;//栈的大小
private int[] stack;//数组模拟栈,数据存储在该数组中
private int top = -1; //表示栈顶 初始化为-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;
}
//入栈
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("栈空,没有数据");
return;
}
for (int i = top;i>=0;i--){
System.out.println("索引是"+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 var){
return var == '+' || var == '-' || var == '*' || var == '/';
}
//返回当前栈顶的值,但不是真正的出栈
public int pick(){
return stack[top];
}
//计算方法
public int cal(int num1,int num2,int oper){ //int char是能进行互换的
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;
}
}
缺点 问题
我们发现我们这样数字小一点计算没有问题,但是如果我们的数字较大,会出现什么问题呢?
例如我们80+2*6-4应该等于88 可结果输出:表达式结果是:8
同样的70+2*6-4应该等于78 可输出结果是 表达式结果是:8
是不是有数字超过了10位数,我们计算的时候省略了那个10位数的数字呢?
我们试一试11+2*6-4 结果应该等于19 输出结果是:表达式结果是:9
难道真的是11变成了1进去运算了么?还是说我们ascii码中只有0~9的对应值大于9的字符都没了?
问题解决
我们大概率能知道,这个问题出在我们的数字入栈上,我们数字入栈的操作是这样的
else {
//如果是数字,则直接入数栈
numStack.push(ch-48);//ch是作为字符传入的 1 对应的是49 3 对应的是51,相差48,我们要减去48
}
它是一位一位扫描的,两位数只会扫进去一个,也就是我们的问题出在数字入栈上
解决思路:
1.当处理多位数时,不能发现是一个数就立即入栈,有可能是多位数,我们得判断下面的字符是什么
2.处理数是,向后一位index+1判断,是不是数,是符号则入栈,如果是数字,则要进行拼接
3.定义一个字符串变量,用于多位数的时候进行拼接
代码:
else {
//处理多位数
keepNum += ch;
//判断下一个字符是不是数字,是数字继续扫描,运算符,则入栈
if (operStack.isOper(expression.substring(index+1,index+2).charAt(0)))
{
//后面是运算符,怎入栈
numStack.push(Integer.parseInt(keepNum));
//将我们的keepNum清空
keepNum = "";
}
numStack.push(ch-48);//ch是作为字符传入的 1 对应的是49 3 对应的是51,相差48,我们要减去48
}
运行发现:
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 9
at java.lang.String.substring(String.java:1963)
at com.wang.stack.Calculator.main(Calculator.java:57)
数组越界?为什么呢?因为我们少考虑了当前的ch如果是最后一位呢?,index还能往后+么,显然是不能的
//处理多位数
keepNum += ch;
//如果ch已经是字符串表达式最后一位
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清空
keepNum = "";
}
}
最终代码
package com.wang.stack;
/**
* @author 王庆华
* @version 1.0
* @date 2020/12/18 21:41
* @Description TODO
* @pojectname 算法代码 计算器
*/
public class Calculator {
public static void main(String[] args) {
//构建表达式
String expression = "10+2*6-4";
//创建两个栈一个数栈,一个符号栈
ArrayStack2 numStack = new ArrayStack2(10);
ArrayStack2 operStack = new ArrayStack2(10);
//定义需要的相关变量
int index = 0; //用于帮助我们扫描
int num1 = 0;
int num2 = 0;
int oper = 0; //扫描到的操作符
int res = 0; //结果
char ch = ' '; //扫描到的字符
String keepNum = "";//用于拼接字符串
//开始循环扫描
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.pick())){
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
res = numStack.cal(num1,num2,oper);
//运算结果入栈
numStack.push(res);
//当前运算符入符号栈
operStack.push(ch);
}else {
//如果符号栈不为空(有操作符)且当前扫描到的运算优先级大于栈中的操作符,直接入符号栈
operStack.push(ch);
}
}else
{
//符号栈为空,直接入栈
operStack.push(ch);
}
}else {
//处理多位数
keepNum += ch;
//如果ch已经是字符串表达式最后一位
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清空
keepNum = "";
}
}
}
//idnex+1,判断是否到我们字符串表达式的最后
index++;
if (index>= expression.length()){
break;
}
}
//遍历完了我们的字符串表达式
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.println("表达式结果是:"+numStack.pop());
}
}
//拿了我们以前的数组实现栈的代码
//顶一个ArrayStack 表示我们的栈
//但是我们要增加一些功能 如判断优先级等等
class ArrayStack2{
private int maxSize;//栈的大小
private int[] stack;//数组模拟栈,数据存储在该数组中
private int top = -1; //表示栈顶 初始化为-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;
}
//入栈
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("栈空,没有数据");
return;
}
for (int i = top;i>=0;i--){
System.out.println("索引是"+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 var){
return var == '+' || var == '-' || var == '*' || var == '/';
}
//返回当前栈顶的值,但不是真正的出栈
public int pick(){
return stack[top];
}
//计算方法
public int cal(int num1,int num2,int oper){ //int char是能进行互换的
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;
}
}
问题引入:我们的表达式中要是有括号呢?后缀表达式又是什么样呢?逆波兰计算器又是什么样的呢?我会在下个博客学习笔记写出