目录结构
1.JAVA中栈的实现
- 数组实现
- 链表实现
- JAVA自带Stack类
2.计算器的实现
- 中缀表达式(人脑熟悉)
- 前缀表达式(波兰表达式)
- 后缀表达式(逆波兰表达式) 常用
3.中缀转后缀
JAVA中栈的实现
1.数组实现
定义类ArrayList,模拟栈
此类具有三个属性:
private int maxSize; //栈大小
private int[] stack; //实现栈所用的数组 在构造方法中初始化
private int top=-1; //栈顶指针,初始值为-1
具体代码如下:
import java.util.Scanner;
public class ArrayStackDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
ArrayStack stack = new ArrayStack(4);
boolean flag = true;
Scanner scanner = new Scanner(System.in);
String key="";
while(flag){
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":
int i =stack.pop();
System.out.println("出栈的数为"+i);
break;
case "exit":
scanner.close();
flag=false;
System.out.println("退出系统");
default:
break;
}
}
}
}
/**
*实现栈的类
*/
class ArrayStack{
private int maxSize;
private int[] stack;
private int top=-1;
ArrayStack(int maxSize){
this.maxSize=maxSize;
stack = new int[maxSize];
}
/**
* 判断是否为空
* @return
*/
public boolean isEmpty(){
return top==-1;
}
/**
* 判断是否已满
* @return
*/
public boolean isFull(){
return top==maxSize;
}
/**
* 入栈
* @param i
*/
public void push(int i){
if(isFull()){
System.out.println("栈已满,添加失败");
return;
}
top++;
stack[top]=i;
}
/**
* 出栈
* @return
*/
public int pop() throws RuntimeException{
if(isEmpty()){
throw new RuntimeException("栈空,不能再出栈");
}
int value = stack[top];
top--;
return value;
}
/**
* 返回栈顶元素 不出栈
* @return
*/
public int getTop(){
return stack[top];
}
/**
* 显示数据
*/
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]);
}
}
}
2.链表实现
定义类Node,模拟栈中的数据结点
此类只有两个属性:
private int data;
private Node next; //指向下一个结点的指针
定义类ArrayStack2,模拟栈的实现
此类中有一个属性,是整个栈的头结点
//初始化头结点
private Node head=new Node(0, null);
具体代码如下:
import java.util.Scanner;
public class ArrayStackDemoLinked {
public static void main(String[] args) {
// TODO Auto-generated method stub
ArrayStack2 stack = new ArrayStack2();
boolean flag = true;
Scanner scanner = new Scanner(System.in);
String key="";
while(flag){
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":
int i =stack.pop();
System.out.println("出栈的数为"+i);
break;
case "exit":
scanner.close();
flag=false;
System.out.println("退出系统");
default:
break;
}
}
}
}
/**
* 结点类
* @author 10405
*
*/
class Node{
private int data;
private Node next;
Node(int data,Node node){
this.data=data;
this.next=node;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
/**
* 栈类
* @author 10405
*
*/
class ArrayStack2{
//初始化头结点
private Node head=new Node(0, null);
/**
* 判断栈是否为空
* @return
*/
public boolean isEmpty(){
return head.getNext()==null;
}
/**
* 入栈 使用头插法
* @param value
*/
public void push(int value){
Node temp = new Node(value, null);
temp.setNext(head.getNext());
head.setNext(temp);
}
public int pop(){
if(isEmpty()){
throw new RuntimeException("栈为空,不能出栈");
}
Node temp =head.getNext();
head.setNext(temp.getNext());
return temp.getData();
}
public void list(){
if(isEmpty()){
System.out.println("链表为空");
}
Node temp=head.getNext();
while(temp!=null){
System.out.printf("%d\n",temp.getData());
temp=temp.getNext();
}
}
}
3.JAVA自带的Stack类
继承自Vector类
具有如下方法:
计算器的实现
1.使用中缀表达式
中缀表达式是我们常见的书写格式,如:“30+2*50-10=?”。虽然这种方式人为理解很容易,但对计算机很不友好。
思路分析:
1.从 左向右 扫描中缀表达式,通过一个index索引来遍历
2.new 两个栈 s1->数栈 用来存放数字 s2->符号栈 用来存放运算符
3.若当前扫描到的字符是数字,则直接入数栈
4.若当前扫描到的字符是符号,则分情况讨论:
1)若当前符号栈为空,则直接入栈
2)否则进行优先级比较:
① 若当前操作符优先级<=s1栈顶元素的优先级,则从s1 pop一个操作符 s2 pop两个操作数出来,参与运算,并将结果压入数栈。 再将当前操作符压入符号栈s1.
>注:若是减法或除法运算,应该是 次栈顶-栈顶
②否则(当前符号优先级大于符号栈栈顶优先级),则直接入符号栈s1
5.若当前表达式扫描完毕,就顺序从数栈和符号栈pop出相应的数和符号,并运行,结果压入数栈s2
6.到最后,符号栈为空,数栈只有一个数字,就是运算最终结果
2.前缀表达式(波兰表达式)
前缀表达式的操作符都位于操作数之前
例:“(3+4)*5-6”对应的前缀表达式为: “- * + 3 4 5 6”
对于中缀表达式如何转化为前缀表达式,我们这里不进行详述
给定一个前缀表达式,具体的求值过程如下:
1.从 右向左 扫描前缀表达式
2.只new 一个新的Stack 用来存放操作数,操作符不需要入栈
2.遇到数字,直接入栈
3.遇到操作符,则出栈两个操作数进行运算,结果入数栈
>注:若是减法或除法运算,应该是 栈顶-次栈顶
4.重复上述过程,直至扫描完毕
5.最后栈中的唯一元素即是运算结果
3.后缀表达式(逆波兰表达式) 最常用
给定一个后缀表达式,具体求值过程如下:
1.从 左向右 扫描后缀表达式
2.只new 一个新的Stack 用来存放操作数,操作符不需要入栈
2.遇到数字,直接入栈
3.遇到操作符,则出栈两个操作数进行运算,结果入数栈
>注:若是减法或除法运算,应该是 次栈顶-栈顶
4.重复上述过程,直至扫描完毕
5.最后栈中的唯一元素即是运算结果
中缀转后缀表达式算法
给定一个中缀表达式:
1.从 左向右 扫描扫描中缀表达式
2.初始化两个栈 s1、s2 s1是运算符栈 s2是存储中间及最后结果的栈
3.如果遇到数字,直接压入s2
4.否则如果是括号
1)左括号直接入栈s1
2)右括号的话,依次弹出s1栈顶操作符,并压入s2中,直至遇到“(”为止,并丢弃这对括号
5.否则(遇到了操作符),比较其与s1栈顶元素优先级:
1)若s1为空,或栈顶为左括号,则直接入栈s1
2)否则,若优先级比s1栈顶高,也直接压入栈s1
3)否则,将s1栈顶元素弹出压入s2,再重复5.1,与s1中新的栈顶元素比较
6.重复3-5步骤,直到扫描完毕
7.将s1剩余元素依次弹出,并压入s2
8.将s2依次出栈并输出,结果的 逆序 即为该中缀对应的后缀表达式
注:我们观察到 s2中只有入栈没有出栈,且最后出栈后需要倒序排序。所以,在代码的具体实现时,我们可以用ArrayList来代替Stack s2.输出的ArrayList即为正确结果,不用再逆序排序。
具体代码如下:
package com.atguigu.stack;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class PolandNotation {
public static void main(String[] args) {
// TODO Auto-generated method stub
/*//先直接给出逆波兰表达式 (30+4)*5-6
String suffixExpression="30 4 + 5 * 6 - ";
//思路:1.先将表达式拆分开 存入List集合中
// 2.将ArrayList传递给一个方法,在方法中遍历该集合,配合栈 完成计算
List<String> stringList = PolandNotation.getStringList(suffixExpression);
System.out.println(stringList);
int res=PolandNotation.calculator(stringList);
System.out.println(res);
*/
String expression = "1+((20+30)*4)-5";//注意表达式
List<String> infixExpressionList = toInfixExpressionList(expression);
System.out.println("中缀表达式对应的List=" + infixExpressionList); // ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]
List<String> suffixExpreesionList = parseSuffixExpreesionList(infixExpressionList);
System.out.println("后缀表达式对应的List" + suffixExpreesionList); //ArrayList [1,2,3,+,4,*,+,5,–]
System.out.printf("expression=%d", calculator(suffixExpreesionList)); // ?
}
/**
* 将中缀表达式转换成一个List集合
* @param s
* @return
*/
public static List<String> toInfixExpressionList(String s) {
List<String> ls = new ArrayList<String>();
int i = 0; //这时是一个指针,用于遍历 中缀表达式字符串
String str; // 对多位数的拼接
char c; // 每遍历到一个字符,就放入到c
do {
//如果c是一个非数字,我需要加入到ls
if((c=s.charAt(i)) < 48 || (c=s.charAt(i)) > 57) {
ls.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++;
}
ls.add(str);
}
}while(i < s.length());
return ls;//返回
}
/**
* 即 ArrayList [1,+,(,(,2,+,3,),*,4,),-,5] =》 ArrayList [1,2,3,+,4,*,+,5,–]
* 方法:将得到的中缀表达式对应的List => 后缀表达式对应的List
* @param ls
* @return
*/
public static List<String> parseSuffixExpreesionList(List<String> ls) {
//定义两个栈 s1、s2 s1存放运算符 , s2存放中间及结果
Stack<String> s1 = new Stack<>();
List<String> s2 =new ArrayList<>();//因为s2只进栈不出栈,且最后结果需要倒序排列 所以这里直接用List 直接输出就是结果
//遍历list
for(String s :ls){
//若是数字 直接入栈/List
if(s.matches("\\d+")) {
s2.add(s);
}
//如果是“(”
else if("(".equals(s)){
s1.push(s);
}
//如果是“)”
else if(")".equals(s)){
//从s1依次出栈 并压入s2 直到“(”出栈 并丢弃这对括号
String temp="";
while(!s1.peek().equals("(")){
temp=s1.pop();
s2.add(temp);
}
s1.pop();
}
//如果是操作符 分情况:
else{
/* //如果栈为空或者栈顶为“(”,直接入栈
if(s1.isEmpty()||"(".equals(s1.peek())){
s1.push(s);
}
//如果优先级大于栈顶 直接入栈
else if(PolandNotation.getValue(s)>PolandNotation.getValue(s1.peek())){
s1.push(s);
}
//如果优先级小于等于栈顶 则循环 让s1栈顶出栈 并压入s2 直到栈空或者s优先级大于栈顶元素
else {
while(!s1.isEmpty()&&PolandNotation.getValue(s1.peek())>=PolandNotation.getValue(s)){
s2.add(s1.pop());
}
s1.push(s);
} */
/**
* 经过观察 发现 上面这种写法太麻烦 因为不管怎么样 都有push这一步 只是最后一种情况多了一步
* 下面简写:
*/
while(!s1.isEmpty()&&PolandNotation.getValue(s1.peek())>=PolandNotation.getValue(s)){
s2.add(s1.pop());
}
s1.push(s);
}
}
//将s1中剩余的运算符依次弹出并加入s2
while(s1.size() != 0) {
s2.add(s1.pop());
}
return s2; //注意因为是存放到List, 因此按顺序输出就是对应的后缀表达式对应的List
}
/**
* 返回运算符的优先级
* @param s
* @return
*/
public static int getValue(String s){
int res=0;
switch (s) {
case "+":
res=1;
break;
case "-":
res=1;
break;
case "*":
res=2;
break;
case "/":
res=2;
break;
default:
System.out.println("运算符错误,不能返回优先级"+s);
break;
}
return res;
}
/**
* 将后缀表达式拆分开 成为一个List集合
* @param suffixExpression
* @return
*/
public static List<String> getStringList(String suffixExpression){
//将suffixExp分割
String[] strings = suffixExpression.split(" ");
List<String> stringList =new ArrayList<>();
for(String s :strings){
stringList.add(s);
}
return stringList;
}
/**
* 完成对逆波兰表达式的计算
* 1.从左到右扫描
* 2.遇到数字,直接入栈 注:我们只需要一个栈 因为操作符不用入栈
* 3.遇到运算符 出栈两个数 注:次栈顶 - 栈顶
* @param list
* @return
*/
public static int calculator(List<String> list){
Stack<String> stack=new Stack<>();
for(String s:list){
if(s.matches("\\d+")){ //匹配多位数字
//数字直接入栈
stack.push(s);
}else{
//如果是操作符
int num1 =Integer.parseInt(stack.pop());
int num2 =Integer.parseInt(stack.pop());
int res=0;
switch (s) {
case "+":
res=num1+num2;
break;
case "-":
res=num2-num1;
break;
case "*":
res=num1*num2;
break;
case "/":
res=num2/num1;
break;
default:
System.out.println("操作符有误");
break;
}
stack.push(res+"");
}
}
return Integer.parseInt(stack.pop());
}
}