5. 數據結構之棧

數據結構之棧Stack

棧的應用場景

  • 子程序的調用:在跳往子程序前,會先將下個指令的地址存到堆棧中,直到子程序執行完後再將地址取出,以回到原來的程序中。
  • 處理遞歸調用:和子程序的調用類似,只是除了儲存下一個指令的地址外,也將參數、區域變量等數據存入堆棧中。
  • 表達式的轉換[中綴表達式轉後綴表達式]與求值(實際解決)。
  • 二叉樹的遍歷。
  • 圖形的深度優先(depth 一 first)搜索法。

1. 棧入門

  • 棧是一個 先入後出的有序列表。
  • 棧(stack) 是限制線性表中元素的插入和刪除只能在線性表的同一端進行的一種特殊線性表。 。 允許插入和刪除的一端,爲 變化的一端,稱爲棧頂(Top) ,另一端爲 固定的一端,稱爲棧底(Bottom) 。
  • 根據棧的定義可知 , 最先放入棧中元素在棧底 , 最後放入的元素在棧頂 , 而刪除元素剛好相反 , 最後放入的元素最先刪除,最先放入的元素最後刪除
  • 圖解方式說明出棧(pop) 和入棧(push)
  • 在這裏插入圖片描述

1.1 使用數組模擬棧

/**
 * @author DuanChaojie
 * @date 2020年2月24日 下午7:23:10
 * @version 1.0
 * 通過數組定義的棧
 */
class ArrayStack{
	
	//棧的最大容量
	private int maxSize;

	//來裝數據
	private int[] stack;
	
	//棧頂,初始化爲-1
	private int top = -1;
	
	//構造器,初始化棧
	public ArrayStack(int maxSize) {
		this.maxSize = maxSize;
		stack = new int[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("棧滿,不能push數據");
			return;
		}
		
		top++;
		stack[top] = value;
	}
	
	//出棧的方法pop
	public int pop() {
		if(isEmpty()) {
			/**
			 * 	System.out.println("棧空,不能pop數據");
			 *  return;
			 *  因爲該方法的返回值爲int所以不能通過return;來結束該方法,所以選擇拋出一個異常
			 */
			throw new RuntimeException("棧空,不能pop數據");
		}
		int temp = stack[top];
		top--;
		return temp;
	}
	
	
	//遍歷棧方法showList,從棧頂顯示數據
	public void showList() {
		
		if(isEmpty()) {
			System.out.println("棧空,不能遍歷數據");
			return;
		}
		
		for(int i = top;i>=0;i--) {
			System.out.printf("stack[%d]=%d\n",i,stack[i]);
		}
	}	
}

1.2 測試棧

class ArrayStackDemo {

	public static void main(String[] args) {
		//初始化棧
		ArrayStack stack = new ArrayStack(4);
		
		Scanner scanner = new Scanner(System.in);
		
		boolean flag = true;
		
		String key = "";
		
		while(flag) {
			System.out.println("show:表示顯示程序");
			System.out.println("push:表示添加數據到棧");
			System.out.println("pop:表示取出棧中的數據");
			System.out.println("exit:表示退出程序");
			System.out.println("請您輸入要執行程序的指令...");
			
			key = scanner.next();
		
			switch (key) {
			case "show":
				stack.showList();
				break;
			case "push":
				System.out.println("請輸入一個數添加到stack棧中");
				int result = scanner.nextInt();
				stack.push(result);
				break;
			case "pop":
				stack.pop();
				break;
			case "exit":
				scanner.close();
				flag = false;
				break;
			default:
				break;
			}
		}
		System.out.println("程序退出!");

	}

}

2. 棧實現綜合計算器(中綴表達式)

2.1 思路分析圖

在這裏插入圖片描述

2.2 創建棧(添加一些方法)

增加的方法:

  • public int peek();可以返回當前棧頂的值,但不是真正的pop
  • public int priority(int oper);返回運算符的優先級
  • public boolean isOper(char val);判斷是不是一個運算符
  • public int cal(int num1,int num2,int oper);計算方法
/*
 * 先創建一個棧,使用前面的,修改一下
 * */
class ArrayStack2{
	
	//棧的最大容量
	private int maxSize;

	//來裝數據
	private int[] stack;
	
	//棧頂,初始化爲-1
	private int top = -1;
	
	//構造器,初始化棧
	public ArrayStack2(int maxSize) {
		this.maxSize = maxSize;
		stack = new int[maxSize];
	}
	
	//判斷棧是否滿
	public boolean isFull() {
		return top == maxSize - 1;
	}
	
	//判斷棧是否空
	public boolean isEmpty() {
		return top == -1;
	}
	
	//增加一個方法,可以返回當前棧頂的值,但不是真正的pop
	public int peek() {
		return stack[top];
	}
	

	
	
	//入棧的方法push
	public void push(int value) {
		//先判斷棧是否滿
		if(isFull()) {
			System.out.println("棧滿,不能push數據");
			return;
		}
		
		top++;
		stack[top] = value;
	}
	
	//出棧的方法pop
	public int pop() {
		if(isEmpty()) {
			/**
			 * 	System.out.println("棧空,不能pop數據");
			 *  return;
			 *  因爲該方法的返回值爲int所以不能通過return;來結束該方法,所以選擇拋出一個異常
			 */
			throw new RuntimeException("棧空,不能pop數據");
		}
		int temp = stack[top];
		top--;
		return temp;
	}
	
	
	//遍歷棧方法showList,從棧頂顯示數據
	public void showList() {
		
		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 priority(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 cal(int num1,int num2,int oper) {
		//res用於存放計算的結果
		int res = 0;
		
		switch (oper) {
		case '+':
			res = num1+num2;
			break;
		case '-':
			res = num1-num2;
			break;
		case '*':
			res = num1*num2;
			break;
		case '/':
			res = num1/num2;
			break;
		default:
			break;
		}
		
		return res;
	}

2.3 棧實現綜合計算機–邏輯

public class Calculator {

	public static void main(String[] args) {
		
		//計算機傳來的字符串
		String expression = "300+2*2*2-1";
		
		//需要先創建numStack和operStack
		ArrayStack2 numStack = new ArrayStack2(10);
		ArrayStack2 operStack = new ArrayStack2(10);
		
		int index = 0;//用於掃描
		int num1 = 0;
		int num2 = 0;
		int oper = 0;
		int res = 0;
		char ch = ' ';//將每次掃描得到的char保存到ch
		String keepNum = "";//用於拼接多位數
		
		//開始while循環通過index掃描expreesion
		while(true) {
		
			//得到掃描得到的字符
			ch = expression.substring(index,index+1).charAt(0);
			
			//判斷ch是什麼,然後做出相應的處理
			if(index == 0 && operStack.isOper(ch)) {
				throw new RuntimeException("表達式不規範");
			}
			
			//如果是運算符
			if(operStack.isOper(ch)) {
				if(!operStack.isEmpty()) {
					if(operStack.priority(operStack.peek())>=operStack.priority(ch)) {
						num2 = numStack.pop();
						num1 = numStack.pop();
						oper = operStack.pop();
						res = numStack.cal(num1, num2, oper);
						numStack.push(res);
						index--;
					}else {
						operStack.push(ch);
					}
				}else {
					operStack.push(ch);
				}
				
			}else {
				//如果是數字,直接入數棧
				//numStack.push(ch-48);
				//處理多位數
				keepNum +=ch;
				
				//判斷下一個字符是不是數字,如果是數字,就繼續掃描,如果是運算符,則入棧
				//注意是看後一位,不是index++
				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));
							//keepNum一定要清空,不然會影響後面的計算
							keepNum = "";		
						}					
				}
				
			
			}
			/**測試片段
			System.out.println("numStack");
			numStack.showList();
			System.out.println("operStack");
			operStack.showList();
			 */
	
			index++;
			//判定循環結束
			if(index>=expression.length()) {	
				break;
			}
			
		}
		
		while(true) {
		
			if(operStack.isEmpty()) {
				break;
			}
			num2 = numStack.pop();
			num1 = numStack.pop();
			oper = operStack.pop();
			res = numStack.cal(num1, num2, oper);
			numStack.push(res);
			
		}
		
		int pop = numStack.pop();
		System.out.println(pop);
		System.out.printf("計算: %s = %d",expression,pop);
		

	}

3. 棧實現綜合計算器(後綴表達式)

3.1 逆波蘭表達式的計算

  • public static List<String> getListString(String suffixExpression);將一個逆波蘭表達式,依次將數據和運算符放入到ArrayList中。
  • public static int calculate(List<String> ls);完成逆波蘭表達式的運算
public class PolandNotation {

	public static void main(String[] args) {
	
	}
	
	/**
	 * 將一個逆波蘭表達式,依次將數據和運算符放入到ArrayList中
	 * */
	public static List<String> getListString(String suffixExpression){
		String[] split = suffixExpression.split(" ");
		ArrayList<String> list = new ArrayList<String>();
		for (String ele : split) {
			list.add(ele);
		}
		return list;
	}
	
	
	/**
	 * 完成逆波蘭表達式的運算 
	* */
	public static int calculate(List<String> ls) {
		//創建棧,只需要一個棧即可
		Stack<String> stack = new Stack<String>();
		
		//遍歷逆波蘭表達式
		for (String item : ls) {
			//這裏使用正則表達式運算
			if(item.matches("\\d+")) {//匹配的是多位數
				//入棧
				stack.push(item);
			}else {
				
				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(""+res);
			}
			
		}
		//最後留在stack中的就是運算結果
		return Integer.parseInt(stack.pop());
	}
}

3.2 中綴表達式轉後綴表達式

  • class OperationgetValue();返回一個運算符對應的優先級.
  • public static List<String> toInfixExpressionList(String s);將中綴表達式轉成List。
  • public static List<String> parseSuffixExpressionList(List<String> ls);將得到的中綴表達式對應的list轉成後綴表達式list
public class PolandNotation {

	public static void main(String[] args) {
	}
	


	/**
	 * 將中綴表達式轉成List
	 */
	public static List<String> toInfixExpressionList(String s){
		//定義一個list存放中綴表達式對應的內容
		List<String> ls = new ArrayList<String>();
		//這是一個指針,用於遍歷中綴表達式字符串
		int i = 0;
		//多位數的拼接
		String str;
		//每遍歷到一個字符,就放到c
		char c;
		do {
			//如果c是一個非數字,我需要加入到ls
			if((c=s.charAt(i))<48 || (c=s.charAt(i))>57) {
				ls.add(""+c);
				//i需要後移
				i++;
			}else {
				//如果是數,需要考慮多位數
				str = "";
				
				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;//返回
	}
	
	
	/***
	 * 將得到的中綴表達式對應的list轉成後綴表達式list
	 */
	public static List<String> parseSuffixExpressionList(List<String> ls){
		//定義一個棧一個List
		Stack<String> s1 = new Stack<String>();//符號棧
		
		List<String> s2 = new ArrayList<String>();//儲存中間的結果s2
		
		//遍歷ls
		for (String item : ls) {
			
			//如果是一個數,加入到s2
			if(item.matches("\\d+")) {
				
				s2.add(item);
			}else if(item.equals("(")) {
				
				s1.push(item);
			}else if(item.equals(")")) {
				
				//如果是右括號“)” 則依次彈出s1棧頂的運算符,
				//並壓入s2,直到遇到左括號爲止,此時將這一對括號丟棄
				while(!s1.peek().equals("(")) {
					s2.add(s1.pop());
				}
				
				//將 (  彈出s1,消除小括號
				s1.pop();
				
			}else {
				//當item的優先級小於等於s1棧頂的運算符,將s1棧頂的運算符彈出並加入到s2
				while(s1.size()!=0 && Operation.getValue(s1.peek())>=Operation.getValue(item)) {
					s2.add(s1.pop());
				}
				s1.push(item);
				
				
			}
		}
		
		//將s1中剩餘的運算符依次彈出並加入s2
		while(s1.size()!=0) {
			s2.add(s1.pop());
		}
		
		//注意因爲是存放到List 因此按順序輸出就是對應的後綴表達式的List
		return s2;
		
	}
	
	
	
}

/**
 * 返回一個運算符對應的優先級
 * @author DuanChaojie
 * @date 2020年2月27日 下午8:39:51
 * @version 1.0
 */
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.3 測試逆波蘭計算器

波蘭表達式與逆波蘭表達式

public class PolandNotation {

	public static void main(String[] args) {
		//將中綴表達式轉變成後綴表達式
		String expression = "1+((2+3)*4)-5";
		List<String> list = toInfixExpressionList(expression);
		System.out.println("List="+list);
		List<String> parseSuffixExpressionList = parseSuffixExpressionList(list);
		System.out.println("後綴表達式:"+parseSuffixExpressionList);
		
		String suffixExpression = "";
		for (String string : parseSuffixExpressionList) {
			 suffixExpression += " "+ string;
			 System.out.println(suffixExpression);
		}
		
		/**
		 * 思路:
		 * 	1.先將 "3 4 + 5 * 6 -" 放入到一個ArrayList中
		 *  2.將ArrayList傳遞給一個方法,遍歷ArrayList 配合棧完成計算
		 *  3.添加測試 4*5-8+60+8/2 --> 4 5 * 8 - 60 + 8  2 / +   -->76
		 */
		
		//先定義一個逆波蘭表達式
		//說明爲了方便,逆波蘭表達式的數字和符號使用空格隔開
		//String suffixExpression = "4 5 * 8 - 60 + 8 2 / +";
		
		List<String> rpnList = getListString(suffixExpression.substring(1));
		System.out.println("rpnList:"+rpnList);
		int result = calculate(rpnList);
		System.out.println("運算的結果是:"+result);
		
		
	}
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章