栈的介绍
- 栈(stack),是先入后出的有序列表
- 栈是限制线性表中的元素插入和删除仅仅只能够在同一端进行的特殊线性表。变化的一端称之为top,不变的一端称之为buttom。
- 入栈push,添加元素,最先放入的元素在栈底,最后放入的元素在栈顶。
- 出栈pop,删除元素,最后放入的元素最先删除,最先放入的元素最后删除。从栈顶开始操作
栈的应用
- 子程序的调用:
再跳往子程序之前,会将下一个指令的地址存到栈堆中,直到子程序执行完之后再将地址取出,执行原来的程序 - 处理递归调用:
和子程序调用类似,只是除了存储下一个指令的地址外,也将参数、区域变量、等数据存入堆栈中 - 表达式的转换【中缀表达式与后缀表达式的相互转换】与求值
- 二叉树的遍历
- 图形的深度优先(depth—first)搜索法
表达式的求值
数组模拟栈
思路分析
- 定义一个top表示栈顶
- 入栈操作,当有数据加入到栈时,top++,stack[top] = date;
- 出栈操作,int value = stack[top];top – ,return value;
数组代码实现:
package stack;
import java.util.Scanner;
public class ArrayStack {
public static void main(String[] args) {
Stack s = new Stack(19);
Scanner scan = new Scanner(System.in);
boolean loop = true;
while (loop){
System.out.println("show:表示显示栈");
System.out.println("pop:表示从栈中取出数据(出栈)");
System.out.println("push:表示添加数据到栈中(入栈)");
System.out.println("exit:退出程序");
System.out.println("请输入你的选择");
String key = scan.next();
switch (key){
case "show":
try {
s.show();
break;
} catch (Exception e) {
e.printStackTrace();
break;
}
case "pop":
try {
System.out.println("当前数据是:" + s.pop());
} catch (Exception e) {
e.printStackTrace();
}finally {
break;
}
case "push":
System.out.println("please input the number you want");
int m = scan.nextInt();
s.push(m);
break;
case "exit":
loop = false;
scan.close();
break;
default:
}
}
System.out.println("the programme is over");
}
}
class Stack{
int maxSize;
int top = -1;
int[] stack;
public Stack(int maxSize) {
this.maxSize = maxSize;
stack = new int[maxSize];
}
public boolean isFull(){
return top == (maxSize - 1);
}
public boolean isEmpty(){
return top == -1;
}
public void push(int n){
if(isFull()){
System.out.println("栈已满,不可添加");
return;
}
top ++;
stack[top] = n;
}
public int pop(){
if(isEmpty()){
throw new RuntimeException("是空的,还能怎么出");
}
int value = stack[top];
top -- ;
return value;
//能不能直接return stack【top】,是可以的,不过它出于模仿的话就没有必要的
//pop出栈就意味着栈里面是没有了,但是数组有而已
}
public void show(){
if (isEmpty()){
throw new RuntimeException("空的,没得了");
}
for(int i = top;i >= 0 ; i--){
System.out.println("stack[" + i + "]" +" : " + stack[i]);
}
}
}
创建一个栈:声明最大容量,声明top变量,存储形式数组,构造方法
使用栈来计算表达式–用栈实现综合计算器的功能
设计思路
-
通过一个index值,遍历表达式,同时建立两个栈,一个数栈,一个符号栈
-
如果发现的是一个数字,直接入数栈
-
如果扫描的是符号有如下情况:
- 如果当前符号栈为空,直接入栈
- 如果有操作符,并且当前操作符的优先级小于或者是等于栈中的操作符,从栈中pop出两个数,从符号栈中pop出一个符号,进行运算,将得到的结果入数栈,然后将当前操作符进入符号栈
- 如果有操作符,并且当前操作符的优先级大于栈中的操作符,直接入符号栈
-
当表达式扫描完毕,按照出栈顺序,从数栈和符号中pop出相应的值,运算出的数字入数栈
-
最后在数栈中仅有一个数字,就是表达式的结果
个人理解:/的优先级是最高的,每次弹出都把/先运算,最后再是一次加减。遇到*/将其转换成一个式子来计算±
代码实现:
我的代码:放弃了
package stack;
import java.util.Scanner;
public class Calculater {
public static void main(String[] args) {
String str = "";
Scanner scan = new Scanner(System.in);
System.out.println("请输入你所要输入的表达式,以会车结束");
str = scan.next();
String strnum[] = str.split("[*+-/]");
System.out.println(strnum[1]);
System.out.println(strnum.length);
int[] num = new int[strnum.length];
for(int i =0;i < strnum.length;i ++){
num[i] = Integer.parseInt(strnum[i]);
}
//问题一;不知道怎么用一个数组分开数字和字符?我只会用两个数组
//不好意思,他也不是,因为它计算的是一位数的计算方法,不是两位数,你也只能用两个数组,但是检索符号,可以用string.chaAt()来检索,寻找符号,不会出现正则表达式是错误结果
String[] strSS = str.split("[1-9][0-9]+");
System.out.println(strSS.length);
System.out.println(strSS[1]);
//问题二:我用正则表达式,不能正确的取出符号,每一次都会多一个
NumStack numStack = new NumStack(20);
SymStack symStack = new SymStack(10);
for (int i = 0 ; i < strnum.length;i ++){
numStack.push(num[i]);
System.out.println(num[i]);
System.out.println(strSS[i + 1]);
switch (strSS[i + 1]){
case "*":
if (symStack.top().equals("*")){
//问题三:怎么查看栈顶端的值,而不会出现数组越界的情况?
//答:真笨!不会现判断是否为空吗?兄弟
int k = numStack.pop() * numStack.pop();
numStack.push(k);
symStack.push("*");
}else if(symStack.top().equals("/")){
int k = numStack.pop() / numStack.pop();
numStack.push(k);
symStack.push("*");
}else if(symStack.top().equals("+")){
symStack.push("*");
}else if(symStack.top().equals("-")){
symStack.push("*");
}else if(symStack == null){
symStack.push("*");
}
break;
case "/":
if (symStack.top().equals("*")){
int k = numStack.pop() * numStack.pop();
numStack.push(k);
symStack.push("/");
}else if(symStack.top().equals("/")){
int k = numStack.pop() / numStack.pop();
numStack.push(k);
symStack.push("/");
}else if(symStack.top().equals("+")){
symStack.push("/");
}else if(symStack.top().equals("-")){
symStack.push("/");
}else if(symStack == null){
symStack.push("/");
}
break;
case "+":
if (symStack.top().equals("*")){
int k = numStack.pop() * numStack.pop();
numStack.push(k);
symStack.push("+");
}else if(symStack.top().equals("/")){
int k = numStack.pop() / numStack.pop();
numStack.push(k);
symStack.push("+");
}else if(symStack.top().equals("+")){
int k = numStack.pop() + numStack.pop();
numStack.push(k);
symStack.push("+");
}else if(symStack.top().equals("-")){
int k = numStack.pop() - numStack.pop();
numStack.push(k);
symStack.push("+");
}else if(symStack == null){
symStack.push("+");
}
break;
case "-":
if (symStack.top().equals("*")){
int k = numStack.pop() * numStack.pop();
numStack.push(k);
symStack.push("-");
}else if(symStack.top().equals("/")){
int k = numStack.pop() / numStack.pop();
numStack.push(k);
symStack.push("-");
}else if(symStack.top().equals("+")){
int k = numStack.pop() + numStack.pop();
numStack.push(k);
symStack.push("-");
}else if(symStack.top().equals("-")){
int k = numStack.pop() - numStack.pop();
numStack.push(k);
symStack.push("-");
}else if(symStack == null){
symStack.push("-");
}
break;
}
}
System.out.println(numStack.top);
}
}
class NumStack{
int maxSize;
int top = -1;
int[] ns;
public NumStack(int maxSize) {
this.maxSize = maxSize;
ns = new int[maxSize];
}
public boolean isFull(){
return top == (maxSize - 1);
}
public boolean isEmpty(){
return top == -1;
}
public void push(int m){
if(isFull()){
return;
}
top ++;
ns[top] = m;
}
public int pop(){
if(isEmpty()){
return 0;
}
int temp = ns[top];
top --;
return temp;
}
}
class SymStack{
int maxSize;
int top = -1;
String[] ss;
public SymStack(int maxSize) {
this.maxSize = maxSize;
ss = new String[maxSize];
}
public boolean isEmpty(){
return top == -1;
}
public boolean isFull(){
return top == (maxSize - 1);
}
public void push(String m){
if(isFull()){
return;
}
top ++;
ss[top] = m;
}
public String pop(){
if(isEmpty()){
return "0";
}
String temp = ss[top];
top --;
return temp;
}
public String top(){
return ss[top];
}
}
教程代码:
package stack;
public class Calculater {
public static void main(String[] args) {
String expression = "7*1+3+2/1-2";
//创建两个栈,分别为数栈和符号栈
//没有必要像上次一样创建两个类,在分别对应类创建对应的栈
NSStack numStack = new NSStack(20);
NSStack operStack = new NSStack(20);
//定义相关变量
int index = 0;
//扫描索引
char ch = ' ';
//用于承接扫描的单个字符
int num1 = 0;
int num2 = 0;
int oper = 0;
//为什么定义符号位0,还为int型
int res = 0;//结果
while (true){
//ch = expression.substring(index,index + 1).charAt(0);
//没有必要加上substring的方法生成一个子字符串
ch = expression.charAt(index);
//判断ch是什么
if(operStack.isOper(ch)){
//如果是运算符,总共是三中情况
//情况一;符号栈为空,直接加入
if(operStack.isEmpty()){
operStack.push(ch);
//好吧!我忘记了,低转高是自动的,不需要类型强转
}else if(operStack.priority(operStack.top()) < operStack.priority(ch) ){
//情况二:符号栈不为空,并且输入的运算符优先级比符号栈里面的优先级要高
operStack.push(ch);
}else{
//情况三:符号栈不为空,并且输入的运算符优先级比符号栈里面的低
num1 = numStack.pop();
num2 = numStack.pop();
res = numStack.cal(num1,num2,operStack.pop());
operStack.push(ch);
//符号入栈,别忘记
numStack.push(res);
//结果入栈,这个老是忘记
}
}else{
//如果不是运算符,那就直接压入栈内
numStack.push(Integer.parseInt("" + ch));
//这里比较容易错,char转成int是转成unicode码,1在unicode中不是1,是49
}
//放哪?放在判断语句之前,已经是倒数第二个,那就最后一个少判断一次
//放在判断语句之后,倒数第二个,最后一个执行完了才会再继续运行
//循环完毕的条件
if(index == (expression.length() - 1)){
break;
}
index ++;
}
//检索完毕开始运行站内的符号和数字
while (true){
if(operStack.isEmpty()){
break;
}
//判断为空没必要再用top指针,直接有对应的方法
num1 = numStack.pop();
num2 = numStack.pop();
res = operStack.cal(num1,num2,operStack.pop());
numStack.push(res);
}
System.out.println(expression + " " + numStack.pop());
}
}
class NSStack{
int maxSize;
int top = -1;
int[] ss;
public NSStack(int maxSize) {
this.maxSize = maxSize;
ss = new int[maxSize];
}
public boolean isEmpty(){
return top == -1;
}
public boolean isFull(){
return top == (maxSize - 1);
}
public void push(int m){
if(isFull()){
return;
}
top ++;
ss[top] = m;
}
public int pop(){
if(isEmpty()){
return 0;
}
int temp = ss[top];
top --;
return temp;
}
public int top(){
return ss[top];
}
//优先级用数字表示,数字越大,优先级越高
public int priority(int oper){
if(oper == '*' || oper == '/'){
//这里真心没看懂,我知道是转换成对应的unicode码
//但是你在走索的时候就是用的char,为啥在判定优先级的时候
//不用,要去用int.很麻烦,虽然确实也可以
return 1;
}else if(oper == '+' || oper == '-'){
return 0;
}else{
return -1;
}
}
//判断是不是一个运算符
public boolean isOper(char val){
return val == '*' || val == '-' || val == '+' || val == '/';
}
//难道还有等号?应该是吧!没有!他是一个一个string.charAt()
//来遍历表达式,获取符号,比我想得简单
//计算方法
public int cal(int num1 ,int num2,int oper){
int res = 0;
switch (oper){
case '+':
res = num1 + num2;
System.out.println(num1 + "+" + num2 + "=" + res);
break;
case '-':
res = num2 - num1;
System.out.println(num2 + "-" + num1 + "=" + res);
break;
case '*':
res = num1 * num2;
System.out.println(num1 + "*" + num2 + "="+ res);
break;
case '/':
res = num2 / num1;
System.out.println(num2 + "/" + num1 + "="+ res);
break;
default:
break;
}
return res;
//运算注意顺序,加法和乘法是没有的,减法和除法是有的
}
}
总结比较:
- 判断优先级的高低需要方法,扫描区分数字和符号也需要方法,不能每次都写,所以添加到对应的栈里面,当作类自带的方法
- 我怎么能在main方法里卖弄写那么多东西那?为什么那么多方法没有封装到类里面?这个java的特性算是白学了!
- 当中漏了一步,看了半天才看出来,真可笑,你居然还不会debug,笑死人了。
- 逻辑清晰,用得比较多的功能都是封装在方法里面