Java数据结构——栈、中后缀表达式的计算

本文介绍如何使用数组和单链表模拟栈,并演示利用栈计算中缀表达式的值。此外,还介绍了逆波兰计算器的实现及中缀表达式转换为后缀表达式的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、栈:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.数组模拟栈的简单实现:

package stack;

import java.util.Scanner;


public class ArrayStackDemo {

	public static void main(String[] args) {
		//测试栈
		ArrayStack stack = new ArrayStack(5);
		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 res = stack.pop();
					System.out.printf("出栈的数据是:%d \n", res);
				} catch (Exception e) {
					System.out.println(e.getMessage());
				}
				break;
			case "exit":	
				scanner.close();
				loop = false;
				break;
			default:
				break;
			}
		}
		System.out.println("程序退出了!");
	}

}

//表示栈
class ArrayStack{
	private int maxSize;//栈的大小
	private int[] stack;//用数组表示栈
	private int top = -1; //表示栈顶指针
	
	//构造器初始化;
	public ArrayStack(int maxSize) {
		this.maxSize = maxSize;
		this.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;
	}
	
	//出栈pop,将栈顶的数据返回;
	public int pop() {
		if(isEmpty()) {
			//抛出异常
			throw new RuntimeException("栈空,没有数据!"); //抛出异常本身就可以停止方法;不用在加return
		}
		int temp = stack[top];
		top--;
		return temp;
	}
	
	//遍历栈;遍历时需要从栈顶开始显示数据
	public void list() {
		if(isEmpty()) {
			System.out.println("栈空,没有数据!");
			return;
		}
		for (int i = top; i >= 0 ; i--) {
			System.out.printf("stack[%d]=%d \n", i, stack[i]);
		}
	}
	
}

2.单链表模拟栈:

注意单链表的构建是用头插法

关键点:

  • Node temp = top.next;//设置辅助指针让其指向top的下一个节点;
  • top.next = node;//将头指针所指向的下一个结点的地址,赋给新创建结点的next
  • node.next = temp;//将新创建的结点的地址赋给头指针的下一个结点。
package stack;
/**
 * 使用链表模拟栈
 * 1.注意用单链表模拟栈的时候构建单链表使用的是链表的头插法
 */
public class LinkedListStackDemo {

	public static void main(String[] args) {
		Node node1 = new Node(1);
		Node node2 = new Node(2);
		Node node3 = new Node(3);
		Node node4 = new Node(4);
		LinkedListStack stack = new LinkedListStack();
		stack.push(node1);
		stack.push(node2);
		stack.push(node3);
		stack.push(node4);
		stack.show();
		System.out.println("出栈后:");
		stack.pop();
		stack.pop();
		stack.show();
		Node node5 = new Node(5);
		stack.push(node5);
		System.out.println("再次加入节点后:");
		stack.show();
	}

}

//创建stack
class LinkedListStack{
	private Node top = new Node(-1);//初始化栈顶指针
	
	//判断栈空
	public boolean isEmpty() {
		return top.next == null;
	}
	//添加元素,使用的单链表的头插法
	public void push(Node node) {
		if(top.next == null) {//设置第一个节点
			top.next = node;
			return;
		}
		Node temp = top.next;//设置辅助指针让其指向top的下一个节点;
		//将头指针所指向的下一个结点的地址,赋给新创建结点的next
		top.next = node;
		//将新创建的结点的地址赋给头指针的下一个结点
		node.next = temp;
	}
	//出栈pop, 根据构建方法:是从top指针后开始出栈
	public void pop() {
		if(isEmpty()) {
			throw new RuntimeException("栈空,没有数据!"); //抛出异常本身就可以停止方法;不用在加return
		}
		System.out.printf("出栈节点为%d \n", top.next.number);
		top = top.next;
	}
	//遍历栈
	public void show() {
		if(isEmpty()) {
			System.out.println("栈空!");
			return;
		}
		Node temp = top;
		while(temp.next != null) {
			System.out.println(temp.next);
			temp = temp.next;
		}
	}
}

//创建节点
class Node{
	public int number;
	public Node next;
	
	public Node(int number) {
		this.number = number;
	}
	@Override
	public String toString() {
		return "Node [number=" + number + "]";
	}
}

3.使用栈计算中缀表达式的值:

在这里插入图片描述
1)单个数字进行计算的情况:
例:“7+2*6-4”

2)多位数处理方法:
分析思路:
1.当处理多位数时,不能发现是一个数就立即入栈,因为他可能是多位数
2.在处理数字时,需要向expression表达式的index 后在看一位,若是数字就再向后看一位,如果是符号才入栈
3.因此需要一个字符串变量,用于拼接
4.注意!!!!在进行完一次push操作后,要将keepNum(字符串拼接变量)清空。避免影响到后续的判断
5.如果ch已经是表达式的最后一位,那么就直接入栈。需要增加判断,防止越界。

package stack;

/**
 *使用栈来模拟计算器:
 *1.根据分析需要增加运算符的优先级比较
 *2.增加数字和运算符的区分
 */
public class Calulator {

	public static void main(String[] args) {
		String expression = "70+2*6-4";
		ArrayStack2 numStack = new ArrayStack2(10);
		ArrayStack2 operStack = new ArrayStack2(10);
		//定义相关的变量
		int index = 0;//用于扫描表达式的指针变量
		int num1 = 0;
		int num2 = 0;
		int res = 0;
		int oper = 0;
		char ch = ' ';//将每次扫描得到的char保存到ch;
		String keepNum = "";//用于拼接多位数
		
		while(true) {
			//依次得到表达式的每一个字符
			ch = expression.substring(index, index+1).charAt(0);
			//判断ch是什么,然后做相应的处理
			if(operStack.isOper(ch)) {//如果是运算符
				//判断当前的符号栈是否为空
				if(!operStack.isEmpty()) {//不为空,进行下面的操作
					//如果符号栈中有操纵符,就进行比较,如果当前的操作符的优先级小于或者等于符号栈中的操作符,就需要从数栈中pop出两个数
					//再从符号栈中pop出一个符号进行运算得到结果,入数栈,然后将当前的操作符入符号栈
					if(operStack.pirority(ch) <= operStack.pirority(operStack.peek())) {
						num1 = numStack.pop();
						num2 = numStack.pop();
						oper = operStack.pop();
						res = numStack.calulator(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));
					//注意!!!!在进行完一次push操作后,要将keepNum清空。避免影响到后续的判断
					keepNum = "";
				   }
			    }
			}
			index++;//让index + 1,并判断是否扫描到最后
			if(index >= expression.length() ) {
				break;
			}
		}
		
		//当表达式扫描完毕后,就顺序的从符号栈中pop出相应的数字和符号,进行运算
		while(true) {
			//如果符号栈为空,表示计算得到了最后的结果,此时数栈中只有一个数字【结果】
			if(operStack.isEmpty()) {
				break;
			}
			num1 = numStack.pop();
			num2 = numStack.pop();
			oper = operStack.pop();
			res = numStack.calulator(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; //表示栈顶指针
	
	//构造器初始化;
	public ArrayStack2(int maxSize) {
		this.maxSize = maxSize;
		this.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;
	}
	
	//出栈pop,将栈顶的数据返回;
	public int pop() {
		if(isEmpty()) {
			//抛出异常
			throw new RuntimeException("栈空,没有数据!"); //抛出异常本身就可以停止方法;不用在加return
		}
		int temp = stack[top];
		top--;
		return temp;
	}
	
	//遍历栈;遍历时需要从栈顶开始显示数据
	public void list() {
		if(isEmpty()) {
			System.out.println("栈空,没有数据!");
			return;
		}
		for (int i = top; i >= 0 ; i--) {
			System.out.printf("stack[%d]=%d \n", i, stack[i]);
		}
	}
	
	//返回运算符的优先级,优先级是程序员来定,优先级使用数字表示。返回的数字越大,表示优先级越高。
	public int pirority(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 calulator(int num1, int num2, int oper) {
		int res = 0;//用于存放计算的结果
		switch (oper) {
		case '+':
			res = num1 + num2;
			break;
		case '-':
			res = num2 - num1; // 注意计算顺序:num1是先pop出来的,意味着在原始表达式中num2在前
			break;
		case '*':
			res = num1* num2;
			break;
		case '/':
			res = num2 / num1; //注意顺序
			break;
		default:
			break;
		}
		return res;
	}
	
	//可以返回当前栈顶的值,但不是真正的出栈
	public int peek() {
		return stack[top];
	}
	
}

在这里插入图片描述

二、前缀、中缀、后缀表达式:

1.前缀表达式:

在这里插入图片描述
在这里插入图片描述

2.中缀表达式:

在这里插入图片描述

3.后缀表达式:

在这里插入图片描述
在这里插入图片描述

三、实现一个逆波兰计算器:

(1)输入一个逆波兰表达式(后缀表达式),使用栈计算其结果;
(2)支持小括号和多位整数
(3)思路分析:即后缀表达式的计算机求值过程

中缀表达式:(3 + 4)* 5 -6 的后缀表达式为 :3 4 + 5 * 6 -

此处为了方便,传入的后缀表达式是用空个分开的。在转换为list时使用空格进行分割。后缀表达式计算的方法是通用的(传入的是 list)。

package stack;
/**
 *使用栈计算逆波兰表达式(后缀表达式) 
 *
 */

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class PolandNotation {

	public static void main(String[] args) {
		//先定义逆波兰表达式
		
		String suffixExpression = "30 4 + 5 * 6 -";
		//1.先将"3 4 + 5 * 6 -" 放到ArrayList中:(就是将表达式中的数字和运算符都放到list中了)
		//2.将ArrayList传递给一个方法,遍历ArrayList, 然后配合栈, 完成计算
		
		List<String> list = getListString(suffixExpression);
		System.out.println("list = " + list);
		int result = calulate(list);
		System.out.println("计算的结果是:" + result);
	}
	
	//将一个逆波兰表达式, 依次将数据和运算符 放入到ArrayList 中
	public static List<String> getListString(String suffixExpression){
		//将suffixExpression分割
		String[] split = suffixExpression.split(" "); // 按照空格进行了分割,然后将字符串类型存进了list中
		List<String> list = new ArrayList<String>();
		for (String ele : split) {
			list.add(ele);
		}
		return list;
	}
	
	//编写计算的方法:
	public static int calulate(List<String> ls) {
		//创建一个栈,只需要一个栈即可
		Stack<String> stack = new Stack<String>();
		//遍历 ls
		for (String item : ls) {
			//使用正则表达式来取出数
			if(item.matches("\\d+")) {//表示匹配的是多位数
				//入栈
				stack.push(item);
			}else {//表示匹配到了运算符:
				//pop出两个数并进行运算,然后将计算机过在进行入栈
				int num2 = Integer.parseInt(stack.pop());
				int num1 = Integer.parseInt(stack.pop());
				int res  = 0;
				if(item.equals("+")) {
					res = num1 + num2;
				}else if(item.equals("-")) {
					res = num1 - num2;
				}else if(item.equals("*")) {
					res = num1 * num2;
				}else if(item.equals("/")) {
					res = num1 / num2;
				}else {
					throw new RuntimeException("运算符有误!");
				}
				//把拿到的res要入栈
//				stack.push(Integer.toString(res));
				stack.push("" + res);
			}
		}
		//最后留在stack中的数据就是运算结果
		return Integer.parseInt(stack.pop());
	}
}

在这里插入图片描述

四、中缀表达式转后缀表达式:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
1.方法:将中缀表达式转成对应的List
注意判断条件:‘0’[48] -> ‘9’[57],根据ASCI码值来进行判断是否为数字。

	//方法:将中缀表达式转成对应的List
	public static List<String> toInfixExpressionList(String s){
		//定义一个list,存放中缀表达式 对应的内容:
		ArrayList<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++;
			}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;
	}

2.需要增加一个判断运算符优先级的class:

//编写一个类,返回一个运算符对应的优先级
class Operation{
	private static int ADD = 1;
	private static int SUB = 1;
	private static int MUL = 2;
	private static int DIV = 2;
	
	//写一个方法,返回对应优先级的数字
	public static int getValue(String operation) {
		int result = 0;
		switch (operation) {
		case "+":
			result = ADD;
			break;
		case "-":
			result = SUB;
			break;
		case "*":
			result = MUL;
			break;
		case "/":
			result = DIV;
			break;
		default:
			System.out.println("不存在该运算符!");
			break;
		}
		return result;
	}
}

3.方法:将中缀表达式对应的list转化为后缀表达式对应的list

//方法:将中缀表达式对应的list转化为后缀表达式对应的list
	public static List<String> parseSuffixExpressionList(List<String> ls){
		//定义两个栈
		Stack<String> s1 = new Stack<String>();//符号栈
		//说明:因为numstack这个栈,在整个转换过程中,没有pop操作,而且最后还需要逆序输出栈中的元素,
		//所以这里使用List<String> s2,会方便很多 
//		Stack<String> numstack = new Stack<String>();
		ArrayList<String> s2 = new ArrayList<String>();//用于存放结果的s2
			
		//开始遍历中缀表达式的list
		for (String ele : ls) {
			if (ele.matches("\\d+")) {//表示的是匹配多位数字
				s2.add(ele);
			}else if(ele.equals("(")) {//如果是左括号
				s1.push(ele);
			}else if(ele.equals(")")) {//如果是右括号
				while(!s1.peek().equals("(")) {
					s2.add(s1.pop());
				}
				s1.pop();//这时候将s1中的左括号pop了出去,跟正在遍历的右括号一起,这一对括号就消掉了
			}else {//对操作符的判断
				//当ele的优先级小于等于s1栈顶的操作符优先级,
				while(s1.size() !=0 && Operation.getValue(s1.peek()) >= Operation.getValue(ele)) {
					s2.add(s1.pop());
				}
				s1.push(ele);
			}
		}
		
		//将s1中剩余的运算符依次弹出并加入到s2中
		while(s1.size() != 0) {
			s2.add(s1.pop());
		}
		
		return s2; //注意是存放到List,因此按顺序输出就是对应的后缀表达式
	}

示例结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值