Java數據結構與算法 day03 棧

第四章 棧

本章源碼:https://github.com/name365/Java-Data-structure

棧的應用場景和介紹

先看一個例子

請輸入一個表達式

計算式:[7*2*2-5+1-5+3-3] 點擊計算【如下圖】

在這裏插入圖片描述

請問: 計算機底層是如何運算得到結果的?

注意不是簡單的把算式列出運算,因爲我們看這個算式 7 * 2 * 2 - 5, 但是計算機怎麼理解這個算式的(對計算機而言,它接收到的就是一個字符串),我們討論的是這個問題。—>

棧的介紹

1.棧的英文爲(stack)  
2.棧是一個先入後出(FILO-First In Last Out)的有序列表。
    > 先入後出:先進入的數據後出來。

3.(stack)是限制線性表中元素的插入和刪除只能在線性表的同一端進行的一種特殊線性表。允許插入和刪除的一端,爲變化的一端,稱爲棧頂(Top),另一端爲固定的一端,稱爲棧底(Bottom)4.根據棧的定義可知,最先放入棧中元素在棧底,最後放入的元素在棧頂,而刪除元素剛好相反,最後放入的元素最先刪除,最先放入的元素最後刪除。
5.出棧(pop)和入棧(push)的概念(如下圖所示)    

在這裏插入圖片描述
在這裏插入圖片描述

棧的應用場景

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

棧的思路分析及實現

1. 用數組模擬棧的使用,由於棧是一種有序列表,當然可以使用數組的結構來儲存棧的數據內容,下面我們就用數組模擬棧的出棧,入棧等操作。
2. 實現思路分析,示意圖如下:
  1. 使用數組來模擬棧
  2. 定義一個 top 來表示棧頂,初始化 爲 -1

在這裏插入圖片描述

  1. 入棧的操作,當有數據加入到棧時, top++; stack[top] = data;
  2. 出棧的操作, int value = stack[top]; top–, return value;
  3. 代碼如下(用數組描述):
import java.util.Scanner;

public class ArrayStackDemo {

	public static void main(String[] args) {
		// 測試代碼如下:
		// 先創建一個對象---》表示棧
		ArrayStack stack = new ArrayStack(4);
		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; // top表示棧,初始化爲-1

	// 構造器
	public ArrayStack(int maxSize) {
		this.maxSize = maxSize;
		stack = new int[this.maxSize];
	}

	// 棧滿
	public boolean isFull() {
		return top == maxSize - 1;
	}

	// 棧空
	public boolean isEmpty() {
		return top == -1;
	}

	// 入棧
	public void push(int value) {
		// 先判斷棧是否滿
		if (isFull()) {
			System.out.println("棧滿了!!!");
			return;
		}
		top++;
		stack[top] = value;
	}

	// 出棧-pop,將棧頂的數據返回
	public int pop() {
		// 先判斷棧是否空
		if (isEmpty()) {
			// 拋出異常
			throw new RuntimeException("棧空,沒有數據!!!");
		}
		int value = stack[top];
		top--;
		return value;
	}

	// 顯示棧的情況[便利棧],遍歷時,需要從棧頂開始顯示數據
	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]);
		}
	}
}
  • ​ 使用鏈表來模擬棧
import java.util.Scanner;

public class LinkTest {
	public static void main(String[] args) {
		// 測試代碼如下:
		// 先創建一個對象---》表示棧
		LinkedListStack stack = new LinkedListStack();
		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();
				LinkedList list = new LinkedList(value);
				stack.push(list);
				break;
			case "pop":
				try {
					LinkedList 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 LinkedListStack {
	private LinkedList head = new LinkedList(-1); // 先初始化一個頭結點,頭結點不動
	private LinkedList top = null; // 定義一個節點,代表棧頂所指節點

	public LinkedList getHead() { // 創建一個獲取頭結點的方法
		return head;
	}

	// 判斷棧空
	public boolean isEmpty() {
		return top == null;
	}

	public void push(LinkedList num) {
		// 因爲頭結點不能動,所以需要一個輔助節點來完成
		LinkedList temp = head;
		// 對鏈表進行遍歷,遍歷到最後一個節點,然後進行添加節點
		while (true) {
			// 判斷是否爲節點的最後
			if (temp.next == null) {
				// 當爲空時,說明已經是最後一個節點
				break;
			}
			// 如果不爲空,則指針向後移動
			temp = temp.next;
		}
		// 當退出while循環時,說明到了最後一個節點
		// 將節點添加到鏈表的最後
		temp.next = num;
		// 將top指向這個節點
		top = num;
	}

	// 刪除節點從單向鏈表實現的棧中,即出棧
	public LinkedList pop() {
		// 將top指針指向的節點出棧
		// 因爲出棧後,top指針需要向前移動,所以需要一個輔助指針完成出棧
		LinkedList tmp = top;
		// 將top指針向前移動一個
		// 需要重新遍歷鏈表找到top節點的前一個節點,再由top指針指向這個節點
		LinkedList h = head;
		while (true) {
			// 找到top節點的前一個節點
			if (h.next == top) {
				// 說明找到了這個節點
				break;
			}
			// 如果不是,則指針向後移動
			h = h.next;
		}
		// 退出循環後,找到top節點的前一個節點,對top節點進行刪除
		h.next = top.next;
		// 將top指針指向這個節點,完成top指針的前移動作
		top = h;
		return tmp;
	}

	// 顯示棧的情況[遍歷棧],遍歷時,需要從棧頂開始顯示數據,即從鏈表尾遍歷鏈表
	// 將鏈表進行反轉,然後再打印鏈表
	public void list() {
		// 判斷鏈表是否爲空
		if (head.next == null) {
			System.out.println("鏈表爲空!!!");
			return;
		}
		// 因爲頭結點不能動,需要輔助變量來遍歷
		reverseList(head);
		LinkedList temp = head.next;
		while (true) {
			// 判斷是否到鏈表最後
			if (temp == null) {
				break;
			}
			// 輸出節點信息
			System.out.println(temp);
			// 將temp後移
			temp = temp.next;
		}
	}

	// 棧的遍歷需要先將鏈表進行反轉
	// 鏈表反轉
	public void reverseList(LinkedList head) {
		// 如果當前鏈表爲空,或者只有一個節點,無需反轉,直接返回
		if (head.next == null || head.next.next == null) {
			return;
		}
		// 定義一個輔助的指針(變量),幫助遍歷原來的鏈表
		LinkedList cur = head.next;
		// 定義一個變量存儲當前節點的下個節點,即指向當前節點[cur]的下個節點
		LinkedList next = null;
		// 定義一個新鏈表的頭結點
		LinkedList reverseHead = new LinkedList(-1);
		// 遍歷原來的鏈表,每遍歷一個節點就將其取出,並放在新的鏈表reverseHead的最前端
		while (cur != null) {
			// 暫時保存當前節點的下一個節點(後續需要使用)
			next = cur.next;
			// 將cur的下個節點指向新的鏈表的最前端
			cur.next = reverseHead.next;
			// 將cur連接到新的鏈表上
			reverseHead.next = cur;
			// 讓cur後移
			cur = next;
		}
		// 將head.next指向reverseHead.next,實現單鏈表的反轉
		head.next = reverseHead.next;
	}
}

// 節點
class LinkedList {
	public int val; // 存儲的數據
	public LinkedList next; // 下一個節點

	public LinkedList(int val) {
		this.val = val;
	}

	@Override
	public String toString() {
		return "LinkedList [val=" + val + ", next=" + next + "]";
	}
}

棧實現綜合計算器-思路分析及實現

使用棧完成計算 一個表達式的結果

在這裏插入圖片描述

使用棧完成表達式的計算思路

在這裏插入圖片描述

  1. 通過一個 index 值(索引),來遍歷我們的表達式

  2. 如果我們發現是一個數字, 就直接入數棧

  3. 如果發現掃描到是一個符號, 就分如下情況

    1. 如果發現當前的符號棧爲 空,就直接入棧
    2. 如果符號棧有操作符,就進行比較,如果當前的操作符的優先級小於或者等於棧中的操作符, 就需要從數棧中pop出兩個數,在從符號棧中pop出一個符號,進行運算,將得到結果,入數棧,然後將當前的操作符入符號棧, 如果當前的操作符的優先級大於棧中的操作符, 就直接入符號棧.

在這裏插入圖片描述

  1. 當表達式掃描完畢,就順序的從數棧和符號棧中pop出相應的數和符號,並運行.

  2. 最後在數棧只有一個數字,就是表達式的結果

驗證: 3+2*6-2 = 13

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

代碼實現如下(單位數運算寫法):

public class Calculator {

	public static void main(String[] args) {
		//測試一下
		String exception  = "3+2*6-2";
		//創建兩個棧,一個數棧,一個符號棧
		ArrayStack numS = new ArrayStack(10);
		ArrayStack operS = new ArrayStack(10);
		//定義兩個相關變量
		int index = 0;	//用於掃描
		int num = 0;
		int num2 = 0;
		int oper = 0;
		int res = 0;
		char ch = ' ';	//每次的掃描結果保存到ch
		//k=開始用while語句循環掃描exception
		while(true){
			//依次得到exception 的每一個字符
			ch = exception.substring(index,index+1).charAt(0);
			//判斷ch,做出相應的處理
			if(operS.isOper(ch)){
				//判斷當前的符號棧是否爲空,爲空則入棧
				if(!operS.isEmpty()){
					//如果符號棧有操作符,就進行比較,如果當前的操作符的優先級小於或者等於棧中的操作符,就需要從數棧中pop出兩個數,
					//在從符號棧中pop出一個符號,進行運算,將得到結果,入數棧,然後將當前的操作符入符號棧
					if(operS.priority(ch) <= operS.priority(operS.peek())) {
						num = numS.pop();
						num2 = numS.pop();
						oper = operS.pop();
						res = numS.cal(num, num2, oper);
						//將運算結果入數棧
						numS.push(res);
						//將當前的操作符入符號棧
						operS.push(ch);
					}else{
						//如果當前的優先級大於棧中的操作符,就直接入符號棧
						operS.push(ch);
					}
				}else{
					//如果爲空,則直接入符號棧
					operS.push(ch); 	//1 + 3
				}
			}else{//如果是數,則直接入數棧
//				numS.push(ch); //錯誤的寫法:讀取的是字符不是數字
				numS.push(ch - 48);
			}
			//讓index+1,並判斷是否掃描到exception的最後
			index++;
			if(index >= exception.length()){
				break;
			}
		}
		//當表達式掃描完畢,就順序的從 數棧和符號棧中pop出相應的數和符號,並運行.
		while(true) {
			//如果符號棧爲空,則計算到最後的結果,數棧中只有一個數字[結果]
			if(operS.isEmpty()) {
				break;
			}
			num = numS.pop();
			num2 = numS.pop();
			oper = operS.pop();
			res = numS.cal(num, num2, oper);
			numS.push(res);		//入棧
		}
		//將數棧的最後數,pop出,就是結果
		int res2 = numS.pop();
		System.out.printf("表達式 %s = %d", exception, res2);
	}

}
//先創建一個棧,直接使用前面的
//定義一個類,表示棧
class ArrayStack {
	private int maxSize; // 棧的大小
	private int[] stack; // 數組,模擬棧,數據就放在該數組
	private int top = -1; // top表示棧,初始化爲-1

	// 構造器
	public ArrayStack(int maxSize) {
		this.maxSize = maxSize;
		stack = new int[this.maxSize];
	}
	
	//自定義一個方法,可以返回當前棧頂的值 ,但不是真的出棧
	public int peek(){
		return stack[top];
	}

	// 棧滿
	public boolean isFull() {
		return top == maxSize - 1;
	}

	// 棧空
	public boolean isEmpty() {
		return top == -1;
	}

	// 入棧
	public void push(int value) {
		// 先判斷棧是否滿
		if (isFull()) {
			System.out.println("棧滿了!!!");
			return;
		}
		top++;
		stack[top] = value;
	}

	// 出棧-pop,將棧頂的數據返回
	public int pop() {
		// 先判斷棧是否空
		if (isEmpty()) {
			// 拋出異常
			throw new RuntimeException("棧空,沒有數據!!!");
		}
		int value = stack[top];
		top--;
		return value;
	}

	// 顯示棧的情況[便利棧],遍歷時,需要從棧頂開始顯示數據
	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 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 num,int num2,int oper){
		int res = 0;	//用於存放反計算的結果
		switch(oper){
		case '+':
			res = num + num2;
			break;
		case '-':
			res = num2 - num;	//注意順序
			break;
		case '*':
			res = num * num2;
			break;
		case '/':
			res = num2 / num;
			break;	
		default:
			break;
		}
		return res;
	}
}

多位數運算寫法:

public class Calculator2 {

	public static void main(String[] args) {
		// 測試一下
		String exception = "7*2*2-5+1-5+3-4";
		// 創建兩個棧,一個數棧,一個符號棧
		ArrayStack2 numS = new ArrayStack2(10);
		ArrayStack2 operS = new ArrayStack2(10);
		// 定義兩個相關變量
		int index = 0; // 用於掃描
		int num = 0;
		int num2 = 0;
		int oper = 0;
		int res = 0;
		char ch = ' '; // 每次的掃描結果保存到ch
		String keepNum = ""; // 用於拼接 多位數
		// k=開始用while語句循環掃描exception
		while (true) {
			// 依次得到exception 的每一個字符
			ch = exception.substring(index, index + 1).charAt(0);
			// 判斷ch,做出相應的處理
			if (operS.isOper(ch)) {
				// 判斷當前的符號棧是否爲空,爲空則入棧
				if (!operS.isEmpty()) {
					// 如果符號棧有操作符,就進行比較,如果當前的操作符的優先級小於或者等於棧中的操作符,就需要從數棧中pop出兩個數,
					// 在從符號棧中pop出一個符號,進行運算,將得到結果,入數棧,然後將當前的操作符入符號棧
					if (operS.priority(ch) <= operS.priority(operS.peek())) {
						num = numS.pop();
						num2 = numS.pop();
						oper = operS.pop();
						res = numS.cal(num, num2, oper);
						// 將運算結果入數棧
						numS.push(res);
						// 將當前的操作符入符號棧
						operS.push(ch);
					} else {
						// 如果當前的優先級大於棧中的操作符,就直接入符號棧
						operS.push(ch);
					}
				} else {
					// 如果爲空,則直接入符號棧
					operS.push(ch); // 1 + 3
				}
			} else {// 如果是數,則直接入數棧
				// numS.push(ch); //錯誤的寫法:讀取的是字符不是數字
				// numS.push(ch - 48);
				// 分析思路如下:
				// 1.當處理多位數時,不能發現是一個數就立即入棧,因爲他可能是多位數
				// 2.在處理數,需要向exception的表達式的index 後再看一位,如果是數就進行掃描,如果是符號才入棧
				// 3.因此我們需要定義一個變量 字符串,用於拼接

				// 處理多位數
				keepNum += ch;

				// 如果ch已經是exception的最後一位,就直接入棧
				if (index == exception.length() - 1) {
					numS.push(Integer.parseInt(keepNum));
				} else {
					// 判斷下一個字符是不是數字,如果是數字,就繼續掃描,如果是運算符,則入棧
					// 注意是看後一位,不是index++
					if (operS.isOper(exception.substring(index + 1, index + 2).charAt(0))) {
						// 如果後一位是運算符,則入棧 keepNum = "1" 或者 "123"
						numS.push(Integer.parseInt(keepNum));
						// 注意!!!!!!,keepNum清空
						keepNum = "";

					}
				}
			}
			// 讓index+1,並判斷是否掃描到exception的最後
			index++;
			if (index >= exception.length()) {
				break;
			}
		}
		// 當表達式掃描完畢,就順序的從 數棧和符號棧中pop出相應的數和符號,並運行.
		while (true) {
			// 如果符號棧爲空,則計算到最後的結果,數棧中只有一個數字[結果]
			if (operS.isEmpty()) {
				break;
			}
			num = numS.pop();
			num2 = numS.pop();
			oper = operS.pop();
			res = numS.cal(num, num2, oper);
			numS.push(res); // 入棧
		}
		// 將數棧的最後數,pop出,就是結果
		int res2 = numS.pop();
		System.out.printf("表達式 %s = %d", exception, res2);
	}

}

// 先創建一個棧,直接使用前面的
// 定義一個類,表示棧
class ArrayStack2 {
	private int maxSize; // 棧的大小
	private int[] stack; // 數組,模擬棧,數據就放在該數組
	private int top = -1; // top表示棧,初始化爲-1

	// 構造器
	public ArrayStack2(int maxSize) {
		this.maxSize = maxSize;
		stack = new int[this.maxSize];
	}

	// 自定義一個方法,可以返回當前棧頂的值 ,但不是真的出棧
	public int peek() {
		return stack[top];
	}

	// 棧滿
	public boolean isFull() {
		return top == maxSize - 1;
	}

	// 棧空
	public boolean isEmpty() {
		return top == -1;
	}

	// 入棧
	public void push(int value) {
		// 先判斷棧是否滿
		if (isFull()) {
			System.out.println("棧滿了!!!");
			return;
		}
		top++;
		stack[top] = value;
	}

	// 出棧-pop,將棧頂的數據返回
	public int pop() {
		// 先判斷棧是否空
		if (isEmpty()) {
			// 拋出異常
			throw new RuntimeException("棧空,沒有數據!!!");
		}
		int value = stack[top];
		top--;
		return value;
	}

	// 顯示棧的情況[便利棧],遍歷時,需要從棧頂開始顯示數據
	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 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 num, int num2, int oper) {
		int res = 0; // 用於存放反計算的結果
		switch (oper) {
		case '+':
			res = num + num2;
			break;
		case '-':
			res = num2 - num; // 注意順序
			break;
		case '*':
			res = num * num2;
			break;
		case '/':
			res = num2 / num;
			break;
		default:
			break;
		}
		return res;
	}
}

前綴 中綴 後綴表達式規則

前綴表達式(波蘭表達式)

  1. 前綴表達式又稱波蘭式,前綴表達式的運算符位於操作數之前
  2. 舉例說明: (3+4)×5-6 對應的前綴表達式就是 - × + 3 4 5 6
前綴表達式的計算機求值

從右至左掃描表達式,遇到數字時,將數字壓入堆棧,遇到運算符時,彈出棧頂的兩個數,用運算符對它們做相應的計算(棧頂元素 和 次頂元素),並將結果入棧;重複上述過程直到表達式最左端,最後運算得出的值即爲表達式的結果

例如: (3+4)×5-6 對應的前綴表達式就是 - × + 3 4 5 6 , 針對前綴表達式求值步驟如下:

1.從右至左掃描,將6543壓入堆棧
2.遇到+運算符,因此彈出343爲棧頂元素,4爲次頂元素),計算出3+4的值,得7,再將7入棧
3.接下來是×運算符,因此彈出75,計算出7×5=35,將35入棧
4.最後是-運算符,計算出35-6的值,即29,由此得出最終結果

中綴表達式

  1. 中綴表達式就是常見的運算表達式,如(3+4)×5-6
  2. 中綴表達式的求值是我們人最熟悉的,但是對計算機來說卻不好操作(前面我們講的案例就能看的這個問題),因此,在計算結果時,往往會將中綴表達式轉成其它表達式來操作(一般轉成後綴表達式.)

後綴表達式

  1. 後綴表達式又稱逆波蘭表達式,與前綴表達式相似,只是運算符位於操作數之後
  2. 中舉例說明: (3+4)×5-6 對應的後綴表達式就是 3 4 + 5 × 6 –
  3. 再比如:
正常的表達式 逆波蘭表達式
a+b a b +
a+(b-c) a b c - +
a+(b-c)*d a b c – d * +
a+d*(b-c) a d b c - * +
a=1+3 a 1 3 + =
後綴表達式的計算機求值

從左至右掃描表達式,遇到數字時,將數字壓入堆棧,遇到運算符時,彈出棧頂的兩個數,用運算符對它們做相應的計算(次頂元素 和 棧頂元素),並將結果入棧;重複上述過程直到表達式最右端,最後運算得出的值即爲表達式的結果

例如: (3+4)×5-6 對應的後綴表達式就是 3 4 + 5 × 6 - , 針對後綴表達式求值步驟如下:

1.從左至右掃描,將34壓入堆棧;
2.遇到+運算符,因此彈出434爲棧頂元素,3爲次頂元素),計算出3+4的值,得7,再將7入棧;
3.5入棧;
4.接下來是×運算符,因此彈出57,計算出7×5=35,將35入棧;
5.6入棧;
6.最後是-運算符,計算出35-6的值,即29,由此得出最終結果	

逆波蘭計算器分析和實現

完成一個逆波蘭計算器,要求完成如下任務:

  1. 輸入一個逆波蘭表達式(後綴表達式),使用棧(Stack),計算其結果

  2. 支持小括號和多位數整數,因爲這裏我們主要講的是數據結構,因此計算器進行簡化,只支持對整數的計算。

  3. 思路分析

例如: (3+4)×5-6 對應的後綴表達式就是 3 4 + 5 × 6 - , 針對後綴表達式求值步驟如下:

1.從左至右掃描,將34壓入堆棧;
2.遇到+運算符,因此彈出434爲棧頂元素,3爲次頂元素),計算出3+4的值,得7,再將7入棧;
3.5入棧;
4.接下來是×運算符,因此彈出57,計算出7×5=35,將35入棧;
5.6入棧;
6.最後是-運算符,計算出35-6的值,即29,由此得出最終結果	
  1. 代碼完成
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class PolandNotation {

	public static void main(String[] args) {
		//先定義一個逆波蘭表達式
		//測試1:(3+4)x5-6 =》 3 4 + 5 x 6 -	=》 29
		//測試2:(30+4)x5-6 =》 30 4 + 5 x 6 - =》 164
		//中綴表達式:4 * 5 - 8 + 60 + 8 / 2 =》後綴表達式:4 5 * 8 - 60 + 8 2 / +
		//說明:爲了方便,逆波蘭表達式的數字和符號使用空格隔開
		String suffiException = "3 4 + 5 * 6 -";
		//具體思路:
		//1.先將“ 3 4 + 5 x 6 - ” =》 放到ArrayList中
		//2.將ArrayList傳遞給一個方法,遍歷ArrayList配合棧完成計算
		
		List<String> rpnList = getListString(suffiException);
		System.out.println("rpnList = " + rpnList);
		
		int res = calculate(rpnList);
		System.out.println("計算的結果是:" + res);
	}

	//將一個逆波蘭表達式,一次將數據和運算符放入ArrayList中
	public static List<String> getListString(String suffiException){
		//將suffiException分割
		String[] split = suffiException.split(" ");
		List<String> list = new ArrayList<String>();
		for(String ele:split){
			list.add(ele);
		}
		return list;
	}
	
	//完成對逆波蘭表達式的計算
	/*
	 * 1.從左至右掃描,將3和4壓入堆棧;
	 * 2.遇到+運算符,因此彈出4和3(4爲棧頂元素,3爲次頂元素),計算出3+4的值,得7,再將7入棧;
	 * 3.將5入棧;
	 * 4.接下來是×運算符,因此彈出5和7,計算出7×5=35,將35入棧;
	 * 5.將6入棧;
	 * 6.最後是-運算符,計算出35-6的值,即29,由此得出最終結果	
	 */
	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{
				//pop出兩個數,並運算,再入棧
				int num2 = Integer.parseInt(stack.pop());	//將字符串轉成整數
				int num = Integer.parseInt(stack.pop());
				int res = 0;	//存放判斷結果
				if(item.equals("+")){
					res = num + num2;
				}else if(item.equals("-")){
					res = num - num2;
				}else if(item.equals("*")){
					res = num * num2;
				}else if(item.equals("/")){
					res = num / num2;
				}else{
					throw new RuntimeException("運算符有問題!!!");
				}
				//將res入棧
				stack.push(res + "");	//整數轉字符串
			}
		}
		//最後留在stack中的數據即爲結果
		return Integer.parseInt(stack.pop());	//字符串轉整數
 	}
}

中綴轉後綴表達式思路分析及實現

大家看到,後綴表達式適合計算式進行運算,但是人卻不太容易寫出來,尤其是表達式很長的情況下,因此在開發中,我們需要將 中綴表達式轉成後綴表達式

舉例說明:

將中綴表達式“1+((2+3)×4)-5”轉換爲後綴表達式的過程如下:

  1. 初始化兩個棧:運算符棧s1和儲存中間結果的棧s2;

在這裏插入圖片描述

  1. 從左至右掃描中綴表達式;

  2. 遇到操作數時,將其壓s2;

在這裏插入圖片描述

4. 遇到運算符時,比較其與s1棧頂運算符的優先級:
    (1).如果s1爲空,或棧頂運算符爲左括號“(”,則直接將此運算符入棧;
    (2).否則,若優先級比棧頂運算符的高,也將運算符壓入s1;
    (3).否則,將s1棧頂的運算符彈出並壓入到s2中,再次轉到(4-1)與s1中新的棧頂運算符相比較;

在這裏插入圖片描述

5. 遇到括號時:
   (1) 如果是左括號“(”,則直接壓入s1
   (2) 如果是右括號“)”,則依次彈出s1棧頂的運算符,並壓入s2,直到遇到左括號爲止,此時將這一對括號丟棄.

在這裏插入圖片描述
在這裏插入圖片描述

6. 重複步驟25,直到表達式的最右邊
                           
7. 將s1中剩餘的運算符依次彈出並壓入s2
                           
8. 依次彈出s2中的元素並輸出,結果的逆序即爲中綴表達式對應的後綴表達式.

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

完整步驟:

掃描到的元素 s2(棧底->棧頂) s1 (棧底->棧頂) 說明
1 1 數字,直接入棧
+ 1 + s1爲空,運算符直接入棧
( 1 + ( 左括號,直接入棧
( 1 + ( ( 同上
2 1 2 + ( ( 數字
+ 1 2 + ( ( + s1棧頂爲左括號,運算符直接入棧
3 1 2 3 + ( ( + 數字
) 1 2 3 + + ( 右括號,彈出運算符直至遇到左括號
× 1 2 3 + + ( × s1棧頂爲左括號,運算符直接入棧
4 1 2 3 + 4 + ( × 數字
) 1 2 3 + 4 × + 右括號,彈出運算符直至遇到左括號
- 1 2 3 + 4 × + - -與+優先級相同,因此彈出+,再壓入-
5 1 2 3 + 4 × + 5 - 數字
到達最右端 1 2 3 + 4 × + 5 - s1中剩餘的運算符

因此結果爲

"1 2 3 + 4 × + 5 –"

  • 代碼實現如下:
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class PolandNotation {

	public static void main(String[] args) {

		// 完成將一箇中綴表達式轉成後綴表達式的功能
		// 說明
		// 1.1+((2+3)×4)-5 => 轉成 1 2 3 + 4 × + 5 –
		// 2.因爲直接對str 進行操作,不方便,因此 先將 "1+((2+3)×4)-5" =》 中綴的表達式對應的List
		// 即 "1+((2+3)×4)-5" => ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]

		String exception = "1+((2+3)*4)-5";
		List<String> inList = toInList(exception);
		System.out.println("中綴表達式對應的List = " + inList);
		System.out.println("+++++++++++++++++++++++++++");

		// 3.將得到的中綴表達式對應的List => 後綴表達式對應的List
		// 即 ArrayList [1,+,(,(,2,+,3,),*,4,),-,5] =》 ArrayList
		// [1,2,3,+,4,*,+,5,–]
		List<String> parList = parList(inList);
		System.out.println("後綴表達式對應的List = " + parList);
		
		System.out.printf("expression=%d", calculate(parList));

	}

	// 即 ArrayList [1,+,(,(,2,+,3,),*,4,),-,5] =》 ArrayList [1,2,3,+,4,*,+,5,–]
	// 方法:將得到的中綴表達式對應的List => 後綴表達式對應的List
	public static List<String> parList(List<String> ls) {
		// 定義兩個棧
		Stack<String> s1 = new Stack<String>(); // 符號棧
		// 說明:因爲s2 這個棧,在整個轉換過程中,沒有pop操作,而且後面還需要逆序輸出
		// 因此比較麻煩,這裏就不用 Stack<String> 直接使用 List<String> s2
		// Stack<String> s2 = new Stack<String>(); // 儲存中間結果的棧s2
		List<String> s2 = new ArrayList<String>(); // 儲存中間結果的Lists2

		// 遍歷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.pop();// 將 ( 彈出 s1棧,,消除小括號
			} else {
				// 當item的優先級小於等於s1棧頂運算符,
				// 將s1棧頂的運算符彈出並加入到s2中,再次轉到(4.1)與s1中新的棧頂運算符相比較
				// 問題:我們缺少一個比較優先級高低的方法
				while (s1.size() != 0 && Operation.getValue(s1.peek()) >= Operation.getValue(item)) {
					s2.add(s1.pop());
				}
				// 還需要將item壓入棧
				s1.push(item);
			}
		}
		// 將s1中剩餘的運算符依次彈出並加入s2
		while (s1.size() != 0) {
			s2.add(s1.pop());
		}
		return s2; // 注意因爲是存放到List, 因此按順序輸出就是對應的後綴表達式對應的List
	}

	// 自定義方法:將中綴表達式轉成對應的List
	public static List<String> toInList(String s) {
		// 定義一個List,存放中綴表達式對應的內容
		List<String> ls = new ArrayList<String>();
		int i = 0; // 這是一個指針,用於遍歷中綴表達式字符串
		String str; // 多位數拼接
		char c; // 每遍歷一個字符,就存入c
		do {
			// 如果是非數字,需要加入ls
			if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57) {
				ls.add("" + c);
				i++; // i後移
			} else { // 如果是一個數,需要考慮多位數
				str = ""; // 將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;
	}

	// 將一個逆波蘭表達式,一次將數據和運算符放入ArrayList中
	public static List<String> getListString(String suffiException) {
		// 將suffiException分割
		String[] split = suffiException.split(" ");
		List<String> list = new ArrayList<String>();
		for (String ele : split) {
			list.add(ele);
		}
		return list;
	}

	// 完成對逆波蘭表達式的計算
	/*
	 * 1.從左至右掃描,將3和4壓入堆棧; 2.遇到+運算符,因此彈出4和3(4爲棧頂元素,3爲次頂元素),計算出3+4的值,得7,再將7入棧;
	 * 3.將5入棧; 4.接下來是×運算符,因此彈出5和7,計算出7×5=35,將35入棧; 5.將6入棧;
	 * 6.最後是-運算符,計算出35-6的值,即29,由此得出最終結果
	 */
	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 {
				// pop出兩個數,並運算,再入棧
				int num2 = Integer.parseInt(stack.pop()); // 將字符串轉成整數
				int num = Integer.parseInt(stack.pop());
				int res = 0; // 存放判斷結果
				if (item.equals("+")) {
					res = num + num2;
				} else if (item.equals("-")) {
					res = num - num2;
				} else if (item.equals("*")) {
					res = num * num2;
				} else if (item.equals("/")) {
					res = num / num2;
				} else {
					throw new RuntimeException("運算符有問題!!!");
				}
				// 將res入棧
				stack.push(res + ""); // 整數轉字符串
			}
		}
		// 最後留在stack中的數據即爲結果
		return Integer.parseInt(stack.pop()); // 字符串轉整數
	}
}

// 編寫一個類 Operation 可以返回一個運算符 對應的優先級
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("不存在該運算符:" + operation);
			break;
		}
		return result;
	}
}

完整版逆波蘭計算器

完整版的逆波蘭計算器,功能包括

  1. 支持 + - * / ( )
  2. 多位數,支持小數,
  3. 兼容處理, 過濾任何空白字符,包括空格、製表符、換頁符

注:逆波蘭計算器完整版考慮的因素較多,下面給出完整版代碼供參考學習,其基本思路和前面一樣,也是使用到:中綴表達式轉後綴表達式

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
import java.util.regex.Pattern;

public class ReversePolishMultiCalc {

	/**
	 * 匹配 + - * / ( ) 運算符
	 */
	static final String SYMBOL = "\\+|-|\\*|/|\\(|\\)";

	static final String LEFT = "(";
	static final String RIGHT = ")";
	static final String ADD = "+";
	static final String MINUS = "-";
	static final String TIMES = "*";
	static final String DIVISION = "/";

	/**
	 * 加減 + -
	 */
	static final int LEVEL_01 = 1;
	/**
	 * 乘除 * /
	 */
	static final int LEVEL_02 = 2;

	/**
	 * 括號
	 */
	static final int LEVEL_HIGH = Integer.MAX_VALUE;

	static Stack<String> stack = new Stack<>();
	static List<String> data = Collections.synchronizedList(new ArrayList<String>());

	/**
	  * 
	  * @Description 去除所有空白符
	  */
	public static String replaceAllBlank(String s) {
		// \\s+ 匹配任何空白字符,包括空格、製表符、換頁符等等, 等價於[ \f\n\r\t\v]
		return s.replaceAll("\\s+", "");
	}

	/**
	  * 
	  * @Description 判斷是不是數字 int double long float
	  */
	public static boolean isNumber(String s) {
		Pattern pattern = Pattern.compile("^[-\\+]?[.\\d]*$");
		return pattern.matcher(s).matches();
	}

	/**
	  * 
	  * @Description 判斷是不是運算符
	  */
	public static boolean isSymbol(String s) {
		return s.matches(SYMBOL);
	}

	/**
	  *
	  * @Description 匹配運算等級
	  */
	public static int calcLevel(String s) {
		if ("+".equals(s) || "-".equals(s)) {
			return LEVEL_01;
		} else if ("*".equals(s) || "/".equals(s)) {
			return LEVEL_02;
		}
		return LEVEL_HIGH;
	}

	/**
	 * 匹配
	 */
	public static List<String> doMatch(String s) throws Exception {
		if (s == null || "".equals(s.trim()))
			throw new RuntimeException("data is empty");
		if (!isNumber(s.charAt(0) + ""))
			throw new RuntimeException("data illeagle,start not with a number");

		s = replaceAllBlank(s);

		String each;
		int start = 0;

		for (int i = 0; i < s.length(); i++) {
			if (isSymbol(s.charAt(i) + "")) {
				each = s.charAt(i) + "";
				// 棧爲空,(操作符,或者 操作符優先級大於棧頂優先級 && 操作符優先級不是( )的優先級 及是 ) 不能直接入棧
				if (stack.isEmpty() || LEFT.equals(each)
						|| ((calcLevel(each) > calcLevel(stack.peek())) && calcLevel(each) < LEVEL_HIGH)) {
					stack.push(each);
				} else if (!stack.isEmpty() && calcLevel(each) <= calcLevel(stack.peek())) {
					// 棧非空,操作符優先級小於等於棧頂優先級時出棧入列,直到棧爲空,或者遇到了(,最後操作符入棧
					while (!stack.isEmpty() && calcLevel(each) <= calcLevel(stack.peek())) {
						if (calcLevel(stack.peek()) == LEVEL_HIGH) {
							break;
						}
						data.add(stack.pop());
					}
					stack.push(each);
				} else if (RIGHT.equals(each)) {
					// ) 操作符,依次出棧入列直到空棧或者遇到了第一個)操作符,此時)出棧
					while (!stack.isEmpty() && LEVEL_HIGH >= calcLevel(stack.peek())) {
						if (LEVEL_HIGH == calcLevel(stack.peek())) {
							stack.pop();
							break;
						}
						data.add(stack.pop());
					}
				}
				start = i; // 前一個運算符的位置
			} else if (i == s.length() - 1 || isSymbol(s.charAt(i + 1) + "")) {
				each = start == 0 ? s.substring(start, i + 1) : s.substring(start + 1, i + 1);
				if (isNumber(each)) {
					data.add(each);
					continue;
				}
				throw new RuntimeException("data not match number");
			}
		}
		// 如果棧裏還有元素,此時元素需要依次出棧入列,可以想象棧裏剩下棧頂爲/,棧底爲+,應該依次出棧入列,可以直接翻轉整個stack 添加到隊列
		Collections.reverse(stack);
		data.addAll(new ArrayList<>(stack));

		System.out.println(data);
		return data;
	}

	/**
	 * 算出結果
	 */
	public static Double doCalc(List<String> list) {
		Double d = 0d;
		if (list == null || list.isEmpty()) {
			return null;
		}
		if (list.size() == 1) {
			System.out.println(list);
			d = Double.valueOf(list.get(0));
			return d;
		}
		ArrayList<String> list1 = new ArrayList<>();
		for (int i = 0; i < list.size(); i++) {
			list1.add(list.get(i));
			if (isSymbol(list.get(i))) {
				Double d1 = doTheMath(list.get(i - 2), list.get(i - 1), list.get(i));
				list1.remove(i);
				list1.remove(i - 1);
				list1.set(i - 2, d1 + "");
				list1.addAll(list.subList(i + 1, list.size()));
				break;
			}
		}
		doCalc(list1);
		return d;
	}

	/**
	  * 
	  * @Description 運算
	  */
	public static Double doTheMath(String s1, String s2, String symbol) {
		Double result;
		switch (symbol) {
		case ADD:
			result = Double.valueOf(s1) + Double.valueOf(s2);
			break;
		case MINUS:
			result = Double.valueOf(s1) - Double.valueOf(s2);
			break;
		case TIMES:
			result = Double.valueOf(s1) * Double.valueOf(s2);
			break;
		case DIVISION:
			result = Double.valueOf(s1) / Double.valueOf(s2);
			break;
		default:
			result = null;
		}
		return result;
	}

	public static void main(String[] args) {
		// String math = "9+(3-1)*3+10/2";
		String math = "12.8 + (2 - 3.55)*4+10/5.0";
		try {
			doCalc(doMatch(math));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

本章導圖總結

在這裏插入圖片描述

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