文章目录
一、栈:
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,因此按顺序输出就是对应的后缀表达式
}
示例结果: