数据结构与算法--栈

栈(Stack)

* 栈是一个先入后出(FILO-First In Last Out) 的有序列表;
* 

栈是限制线性表中元素 的插入 与 删除 只能 在线性表的同一端进行的中特殊线性表。其插入与删除一端,称为栈顶(Top),另一端四固定不动的,称之为栈底(Botton);

栈的应用场景:
* 子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
* 处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
* 表达式的转换[中缀表达式转后缀表达式]与求值(实际解决)。
* 二叉树的遍历。 图形的深度优先(depth一first)搜索法

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

用数组模拟栈(结构图如下)

1.先入后出的一种有序列表 :MaxTop (栈的最大容量)、Top(栈顶)
2.入栈
2.1遍历数组到最后一个元素,将其加入数组。(加入前需要判断栈(数组)是否已经满了)
3.出栈
3.1遍历数组到最后一个元素,将其从数组中删除。(删除前需要判断栈(数组)是否为空)

在这里插入图片描述

用数组模拟栈代码

import java.util.Arrays;
import java.util.Scanner;

/*
 * 用数组模拟栈
 * 栈
 * 成员属性: Top 初始值为 -1 ; MaxSize:栈的最大容量 ; 数组stack
 * 方法:出栈 ,入栈 , 显示栈
 * 入栈:放入之前先判断是否栈满
 * 出战:出栈之前需要判断栈是否为空
 */

public class ArrayStackDemo {
	public static void main(String[] args) {
		
		/*
		arrayStack.enter(5);
		arrayStack.enter(3);
		arrayStack.enter(6);
		System.out.println(arrayStack);
		arrayStack.show();
		System.out.println(arrayStack.out());
		*/
		//创建一个栈
		ArrayStack arrayStack =new ArrayStack(5);
		String key = " ";
		Scanner scanner = new Scanner(System.in);
		boolean loop =true; //用于循环开始于结束的标记
		while(loop) {
			System.out.println("enter: 入栈");
			System.out.println("out  : 出栈");
			System.out.println("show : 显示栈");
			System.out.println("exit :退出系统");
			System.out.println("请输出功能选项:");
			key = scanner.next();
			switch (key) {
			case "enter":
				System.out.println("请输入入栈内容: ");
				Object object1 = scanner.next();
				arrayStack.enter(object1);
				break;
				
			case "out":
				Object object2 = arrayStack.out();
				System.out.println("出栈内容: \n" + object2);
				break;
				
			case "show":
				System.out.println("栈中的内容:  ");
				arrayStack.show();
				break;
				
			case "exit":
				System.out.println("退出系统~~~");
				scanner.close();
				loop = false;
				break;
			default:
				break;
			}
			
		}
		
	}
}

class ArrayStack{
	int top;
	int maxSize;
	Object[] stack;
	
	//构造器
	public ArrayStack( int maxSize ) {
		this.top = -1;
		this.maxSize = maxSize;
		this.stack = new Object [this.maxSize];
	}

	@Override
	public String toString() {
		return "ArrayStack [top=" + top + ", maxSize=" + maxSize + ", stack=" + Arrays.toString(stack) + "]";
	}
	
	//入栈
	public void enter(Object data ) {
		//判断数组是否已经满了
		if(top == maxSize-1) { System.out.println("栈满");return;}
		//若没有满,让栈顶移动一位(top=top+1),再存放数据 stack[top] = data;
		top +=1;
		stack[top] = data;	
	}
	//显示栈
	public void show() {
		for(int i =top ; i >= 0  ;--i) {
			System.out.println("stack[%d]".formatted(i) + " = " + stack[i] );
		}
	}
	//出栈
	public Object out() {
		if(top == -1 ) {System.out.println("栈空");return null;}
		Object value = stack[top] ;
		stack[top]=null;
		top--;
		return value;
		
	}
	
}

运行结果:
在这里插入图片描述

栈实现综合计算器:(运算符优先级自行给出)

简化思路: 3+26-2 、 30+26-2、 722-5+1-5+3-4
使用栈来完成表达式的计算思路(用两个栈来实现,一个存放数字,一个存放运算符)
*
1.遍历表达式:通过一个index辅助变量
*
2.如果index辅助变量指向是一个数字,就直接放入栈中
*
3.如果index辅助变量指向是一个符号,就要分情况:
*
3.1如果符号栈(operStack)为空,就直接 将其符号 加入 符号栈 中
*
3.2如果符号栈中不为空,就要进行符号优先级比较:
*
3.2.1如果当前的操作符的优先级 比 栈中的 操作符 的优先级 小 或者相等,则需要将数栈出两个数,符号栈也出一个符号,进行运算得到的结果res将其放入 数栈 中,然后将当前的符号 放入 符号栈 中
*
3.2.2如果当前的操作符的优先级 比 栈中的 操作符 的优先级 大,则直接入符号栈。
*
4.当表达式遍历完,就顺序得从 数栈 和符号栈出相应的数 和 符号,进行运算,得到表达式 最后的结果 ,并将其放入 数栈中

在这里插入图片描述
在这里插入图片描述
问题1:如何判断运算符的优先级。
自定义优先级: 利用数字大小,比较,数字越大,优先级越大。
问题2:由于遍历表达式得到的类型是String ,计算是需要转换类型 考虑到String类 、基本数据类型与包装类的转换问题
基本类数据类型 和 包装类----->String类:
String str = String.valueof(基本数据类型和包装类);
String类 ----> char[] : str.toChar ;
String 类本事就是由char数构成 String 类只需要某一段字符:str.charAt(index); 或者 str.charAt(index1 ; index2);
String 类 -------->基本类数据类型 和 包装类:
调用相对的包装类的parseXxx(string) int i2 = Integer.parseInt(str1); System.out.println(i2);
boolean b1 = Boolean.parseBoolean(str3); System.out.println(b1);
问题3:当出现多位数情况(怎么放入栈)
由于表达式是逐个遍历的,当遍历到数时,循环判断下一个是否也是数字。若是需要将其拼接,再放入数栈。

栈实现综合计算器代码

import java.util.Arrays;



public class Calculator {
	
	public static void main(String[] args) {
		String expresion = "100/50+22*300+2*60-200" ; 
	
		//String类是一个字符数组 遍历表达式需要辅助变量
		int index = 0 ;
		char ch;   //遍历表达式
		int num1 ; //数栈取出的第一个数字
		int num2 ; //数栈取出的第二个数字
		String res ;  //运算结果
		char operation;
		//遍历出来的字符需要放入两个栈中:数栈(numStack) 、 符号栈(operStack)
		ArrayStack2 numStack = new ArrayStack2(10);
		ArrayStack2 operStack = new ArrayStack2(10);
		
		String string =""; //用于数字拼接
		ch = expresion.charAt(index);
		
		while(index <= expresion.length()-1) {
/*
			//System.out.println(index +"  " + (expresion.length()-1)) ;
			//System.out.println(ch);
			//numStack.show();
			//operStack.show();
			//if (index >= expresion.length()-1) {System.out.println("遍历表达式完毕");break;}	
			//进行判断是否为数字,或者是否符号
			//若是数字,需要对接下来的表达式进行遍历进行判断是否也是数字
*/
			if (numStack.isdigit(ch)) {
				//先测试相连下一个是否为数字,若是,就继续判断接下来的表达式
				while(numStack.isdigit(ch)) {
					//若是到了表达式最后就结束循环
					string += ch;
					//System.out.println("shuzi"+ch);
					index +=1;
					if(index > expresion.length()-1) {break;}
					ch = expresion.charAt(index);
					
				}
				numStack.enter(string);
				//当拼接完,需要对string 清空,为保证下一个多位数的正确、
				string = "";
			}else if (operStack.isOper(ch)) {
				//判断是否为空栈
				  if(operStack.top == -1 ) {
					operStack.enter(ch);
					index +=1;
					ch = expresion.charAt(index);
			    //若不是空栈,需要进行比较运算符优先级大小
					}else 
						if(operStack.priority(ch) <= operStack.priority(operStack.getTop())){
					//System.out.println("numStack: "  );
					//numStack.show();
			     	//若	当前的操作符的优先级 比 栈中的 操作符 的优先级 小 或者相等,需要将数栈出两个数,符号栈也出一个符号,进行运算得到的结果res将其放入 数栈 中,然后将当前的符号 放入 符号栈 中
					num1 = Integer.parseInt((String) numStack.out());
					num2 =  Integer.parseInt((String)numStack.out());   //由于用Object 数组来模拟栈 ,需要类型转换
					operation = (char) operStack.out();
					//取出两个数,一个符号 进行运行
					res =calculation(num1, num2, operation);
				
					//得出结果将其放入数栈 ,再将当前符号放入 符号栈
					numStack.enter(res);	
					operStack.enter(ch);
					
					//遍历表达式的辅助变量后移
					index +=1;
					ch = expresion.charAt(index);
				//若	当前的操作符的优先级 比 栈中的 操作符 的优先级 大,则直接入符号栈。
					}
						else {
						operStack.enter(ch);
						//遍历表达式的辅助变量后移
						index +=1;
						ch = expresion.charAt(index);
					}
				}	
			
			}
			
		/*
		 * 一开始计算方法:使用int类型 ; 导致了计算结果放入数栈中 含有不同的类型数据,使最后的计算需要进行类型转换
		 * 类型的转换涉及了  基本数据类型 、包装类 、 String类 
		 * 类型的转换并不是很难(自己的知识不熟悉),困难在于提取出来就需要判断,不知道用什么来保存进行判断,虽然可以直接判断但数据就没有得到保留进行计算
		 */
		    //当表达式遍历完,数栈会还有两个数,符号栈还有一个数,将其取出来运算,再将最终结果放入数栈中
			//System.out.println(numStack.out().getClass().toString() );
			//if(numStack.out() instanceof String ) {System.out.println("numStack.out().getClass().toString() == \"Interge\"");}
			//num1 = Integer.parseInt(( String)numStack.out());
			//num2 = numStack.out();
			//ch =(char)operStack.out();
			//res = calculation(num1, num2, ch );
			//numStack.enter(res);
			
			//当遍历完表达式,就需要将数栈与符号栈依次序将其计算出来
			
		 	numStack.show();
		 	operStack.show();
			while(true) {
				if(operStack.top == -1) {break;}
				num1 =Integer.parseInt((String)numStack.out()) ;
				num2 =Integer.parseInt((String)numStack.out()) ;
				operation = (char) operStack.out();
				res = calculation(num1, num2, operation);
				numStack.enter(res);		
			}	
			System.out.println("表达式:%s = ".formatted(expresion) +  numStack.out());
		}

	public static String  calculation(int num1 , int num2 , char operation) {
		switch (operation) {
		case '+':
			return String.valueOf(num1 + num2);
		case '-':
			return String.valueOf(num2 - num1);
		case '*':
			return String.valueOf( num1 * num2);
		case '/':
			return String.valueOf(num2 / num1) ;
		default:
			throw new  RuntimeException("该符号暂时没有定义算法");
		}
	}
}


//创建栈类
class ArrayStack2{
	int top;
	int maxSize;
	Object[] stack;
	
	//构造器
	public ArrayStack2( int maxSize ) {
		this.top = -1;
		this.maxSize = maxSize;
		this.stack = new Object [this.maxSize];
	}

	@Override
	public String toString() {
		return "ArrayStack [top=" + top + ", maxSize=" + maxSize + ", stack=" + Arrays.toString(stack) + "]";
	}
	
	//入栈
	public void enter(Object data ) {
		//判断数组是否已经满了
		if(top == maxSize-1) { System.out.println("栈满");return;}
		//若没有满,让栈顶移动一位(top=top+1),再存放数据 stack[top] = data;
		top +=1;
		stack[top] = data;	
	}
	//显示栈
	public void show() {
		if(top == -1 ) {System.out.println("show栈空");return ;}
		for(int i =top ; i >= 0  ;--i) {
			System.out.println("stack[%d]".formatted(i) + " = " + stack[i] );
		}
	}
	//出栈
	public Object out() {
		if(top == -1 ) {System.out.println("out栈空");return null;}
		Object value = stack[top] ;
		stack[top]=null;
		top--;
		return value;
		
	}
	//获取栈顶,但不是出栈,用于运算符优先级比较
	public char getTop() {
		return (char)stack[top];
	}
	
	//判断是否为运算符  参数待定可能是char类型 也可能是String
	public boolean isOper(char ch ) {
		return ch == '+' || ch == '-'|| ch == '*'|| ch == '/';
	}
	//判断是否为数字
	public boolean isdigit(char ch ) {
		return ch>='0' && ch <= '9';
	}
	
	//自定义运算符优先级 ,数越大 优先级越大
	public int priority(char ch) {
		if(ch =='+'  || ch == '-') {
			return 0;
		}else if(ch == '*' || ch == '/') {
			return 1;
		}else {
			return -1; //表示目前还没有自定义该符号的优先级大小
		}
		
	}
}



运行结果:
在这里插入图片描述

后缀表达式计算

中缀表达式转换为后缀表达式:

中缀表达式,就是平时看到计算表达式一样。比如 : 4+5*(5+8)-4/2
后缀表达式:运算符在运算数后面。运算符前面两个数字是该运算符的运算数(本身表达式的数,以及优先级大的没计算出的数)。 比如:4+5*(5+8)-4/2 ----> 4 5 5 8 + * + 4 2 / -
代码具体步骤:
1)初始化两个栈,运算符栈stack1 和 储存后缀表达式的栈stack2;
2)从左向右扫描中缀表达式;
3)遇到数字直接stack2栈;
4)遇到运算符时,比较与stack1栈顶的运算符的优先级
4.1)如果stack1为空,或者遇到栈顶为”(“左括号运算符,就直接入栈stack1栈;
4.2)如果stacj1不为空,则当前的运算符与比较栈顶运算符优先级;
4.2.1)当前的运算符 比 栈顶运算符 的优先级 大 ;则直接入stack1栈;
4.2.2)当前的运算符 比 栈顶运算符 的优先级 小或者相等,则将栈顶的运算符弹出并且将其压入stack2栈中,此时当前运算符与 stack1的新的栈顶运算符进行比较。重复 4.2.1)和4.2.2)步骤;
5) 当遇到括号时:
5.1)如果遇到是”( “ 左括号时,则直接入stack1栈 ;
5.2)如果遇到是” )“有口号时,则stack1栈顶依次弹出运算符,并且将其压入stack2栈中,直到 stack1栈顶为” ( “ 左括号,同时将其” ( “ 弹出,但不放入stack2栈(此时就可以去掉括号)
6)重复2)-5)步骤,直到将中缀表达式扫面完
7)最后将stack1栈中的剩下的运算符依次压入stack2栈中;
8)后追表达式为stack2栈的逆序
在这里插入图片描述
后缀表达式计算:
1.初始化一个栈;
2.从左向右扫面后缀表达式;
3.遇到数字直接入栈;
4.遇到符号,弹出栈两个数字,进行计算,并将其结果入栈;
5.重复 3 - 4 步骤,直到扫描完后缀表达式,得出后缀表达式的结果;

后缀表达式计算代码

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

/*
 * 后缀表达式的计算
 * 1.中缀表达式转换为后缀表达式
 * 2.利用后缀表达式计算结果
 * 
 */
public class PolanNatation {
	public static void main(String[] args) {
		//String expression = "4+5*(5+8)-4/2";
		//String expression = "(4+5)*(5+8)-4/2";
		String expression = "200/4*4+5*(5+8)-4/2";
		
		PolanCaculator polanCaculator = new PolanCaculator(expression);
		polanCaculator.nifixTopostfix();
		System.out.println(polanCaculator.postfixExpression);
		
	    String res = polanCaculator.caculator();
	    System.out.println("expression : %s =".formatted(expression) + res );
		
	}
	
}


/*
 * 中缀表达式转换为后缀表达式: 4+5*(5+8)-4/2   ----> 4 5 5 8 + * + 4 2 / -
 * 中缀表达式 是由 字符换串 来储存 
 * 后缀表示用ArrayList 来储存,以便添加删除查询
 * 转换原则:(符号由栈来保存,后缀表达式由 ArrayLisst)
 * 需要遍历中缀表达式,
 * 遇到数字直接添到 ArrayList 中保存;
 * 遇到字符
 * 情况1:栈中为空,或者遇到栈顶为 “(” 左括号时, 就直接入栈;
 * 情况2:栈中不为空,当前的字符与栈顶的字符进行比较,情况如下:
 * 情况2.1:当前的字符 比 栈顶的字符 的 优先级 大 , 则直接入栈;
 * 情况2.2:当前的字符 比 栈顶的字符 的 优先级 小或者相等,将栈顶的字符弹出,压入 ArrayList 中;再进行与新的栈顶进行比较,重复此步骤,直到 遇到优先级 ;
 * 情况3:遇到 括号 时 
 * 情况3.1:遇到 “(” 左括号,则直接入栈;
 * 情况3.2:遇到“)”右括号,次序 将 栈顶弹出,加入到ArrayList,直到栈顶为“(”时,将其弹出,此时不加入ArrayList中;
 * 重复以上步骤;直到遍历完中缀表达式;
 * 再依次将栈中的字符,依次加入ArrayList;
 */

class PolanCaculator{
	String  nifixExpression ;
	List<String> postfixExpression = new ArrayList<String>();
	//开始遍历中缀表达式;需要一个辅助变量
	int index;
	//需要一个栈来储存字符;
	Stack<String> stack  = new Stack<>();
	Stack<String> caculatorstack  = new Stack<>();
	
	//构造器:需要给中缀表达式赋值
	public PolanCaculator(String nifixString) {
		this.nifixExpression =nifixString;
		index = 0 ;
	}
	
	//开始遍历中缀表达式转换成后缀表达式
	public void nifixTopostfix() {
		String digit = "";  //用于数字的拼接
		int newindex  ;      //用于查下一个字符是否还是数字
		while (true) {
			if (index > nifixExpression.length()-1 ) {
				break;
			} 
			// 遍历到最后一个完结束;
			// 判断是否为数字或者运算符
			/*
			 * 考虑多位数
			 * 
			 */
			if (isDigitOper(nifixExpression.charAt(index))) {
				newindex = index ;
				//由于一个一个遍历,出现多位数被拆开,导致出现后缀表达式出现有误,需要一个循环判断下一个是否还是数字,是的话,将其拼接在一起
				while(isDigitOper(nifixExpression.charAt(newindex))) {		
					digit += nifixExpression.charAt(newindex);
					newindex += 1;
					if(newindex > (nifixExpression.length()-1)) {break;}
					
				}	
				postfixExpression.add(digit); // 由于利用charAt来遍历,charAt()返回一个char类型值
				//添加完,digit必须要清空
				digit="";
				// 辅助变量后移
				index = newindex;
				
			/*
			 *没有考虑到多位数
			
			if (isDigitOper(nifixExpression.charAt(index))) {
				postfixExpression.add(String.valueOf(nifixExpression.charAt(index)));
				index +=1;
			*/
			} else {
				while (true) {
					// 1.判断 栈是否为空,或者 栈顶为 “(” 左括号
					if (isoperEmpty()) {
						stack.add(String.valueOf(nifixExpression.charAt(index)));
						index += 1;
						break;
					} else {
						// 2. 栈不为空 , 栈顶 也不为“(” 左括号 ,则需要进行比较优先级;
						// 当 遇到 括号 不需要比较 优先级
						// 若是遇到" ( "左括号,将其直接入栈
						// 若是遇到" )"右括号,将栈中在 "("左括号之前的符号此次弹出,并且将加入 ArrayList 中
						if(String.valueOf(nifixExpression.charAt(index)).equals(")")) {
							while (String.valueOf(nifixExpression.charAt(index)).equals(")")) {
								
								if (stack.peek().equals("(")) {
									//System.out.println(stack);
									stack.pop();
									index += 1;
									break;
								}else {
									//System.out.println("弹出前" + stack);
									postfixExpression.add(stack.pop());
									///System.out.println("弹出后"+stack);
								}
								System.out.println(nifixExpression.charAt(index));  //由于在一开始由判断栈顶是否为“( ” ,所以在这也需要一个循环
								}
							} else {
							if (String.valueOf(nifixExpression.charAt(index)).equals("(")) {
								stack.add(String.valueOf(nifixExpression.charAt(index)));
								index += 1;
								break;
							} else if (priority(String.valueOf(nifixExpression.charAt(index))) > priority(stack.peek())) {
								// 如果 当前的字符 比 栈顶的字符 优先级 大 ,则直接入栈
								stack.add(String.valueOf(nifixExpression.charAt(index)));
								index += 1;
								break;
							} else {
								// 否则 当前的字符 比 栈顶的字符 优先级 小或者相等 ,则需要将栈顶弹出,并且将其加入到ArrayList
								postfixExpression.add(stack.pop());
							}
						}

					}
				}
			}
		}
		// 当遍历完中缀表达式时,需要将栈中的字符依次弹出,并且加入ArrayList中
		for (int i = 0; i <= stack.size(); i++) {
			postfixExpression.add(stack.pop());
		}
	}
	
	//计算后缀表达式 :由于postfixExpression是含有多位数,不能使用isDigitOper来判断是否为数字 ,其实是可以的,因为多位数第一位也数字
	public String caculator() {
		String res = "";
		for(String string : postfixExpression ) {
			
			if( isDigitOper(string.charAt(0))) {
				//System.out.println(string);
				//若是数字,直接入栈
				caculatorstack.add(string);
			}else {
				//System.out.println("zifu"+string);
				//若是字符则,将战中的两个数弹出来计算
				int num1 = Integer.parseInt( caculatorstack.pop());
				int num2 = Integer.parseInt( caculatorstack.pop());
				res = calculation(num1, num2, string);
				System.out.println("res = " + res );
				caculatorstack.add(res);
			}
		}
		
		return res;
	}
	//判断是否为数字 , 或者 运算符
	public static boolean isDigitOper(char ch) {
		//若是字符则返一个false
		if(ch == '+' || ch == '-' || ch == '*' || ch =='/' || ch == '(' || ch == ')') {return false ;}
		//若是数字则返回一个true
		else if (ch >= '0' && ch <='9') {return true;}
		else {	throw new RuntimeException("该字符尚未定义参与运算");		}	
	}

	//判断栈是否为空,或者 栈顶是否为"("
	public boolean isoperEmpty() {
		if(stack.isEmpty() || stack.peek().equals("(")) { return true;}
		else {return false;}
	}
	
	//比较优先级
	//自定义运算符优先级 ,数越大 优先级越大
	public int priority(String string ) {
		if(string.equals("+")  || string.equals("-")) {
			return 0;
		}else if(string.equals("/")  || string.equals("*")) {
			return 1;
		}else {
			return -1; //表示目前还没有自定义该符号的优先级大小
		}
		
	}

	//计算
	public static String  calculation(int num1 , int num2 , String  operation) {
		switch (operation) {
		case "+":
			return String.valueOf(num1 + num2);
		case "-":
			return String.valueOf(num2 - num1);
		case "*":
			return String.valueOf( num1 * num2);
		case "/":
			return String.valueOf(num2 / num1) ;
		default:
			throw new  RuntimeException("该符号暂时没有定义算法");
		}
	}
}



运行结果:
在这里插入图片描述

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值