JAVA 数据结构之栈与前缀中缀后缀(逆波兰表达式)实现计算器

目录结构

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());		
	}
	
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值