棧
1.棧的定義
- 棧的英文為(stack)
- 棧是一個先入後出(FILO-First In Last Out)的有序清單。
-
棧(stack)是限制線性表中元素的插入和删除隻能線上性表的同一端進行的一種特殊線性表。允許插入和删除的
一端,為變化的一端,稱為棧頂(Top),另一端為固定的一端,稱為棧底(Bottom)。
-
根據棧的定義可知,最先放入棧中元素在棧底,最後放入的元素在棧頂,而删除元素剛好相反,最後放入的元
素最先删除,最先放入的元素最後删除
- 圖解方式說明出棧(pop)和入棧(push)的概念
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2YfNWawNyZuBnLyUjM1QDM1gTMxEDMxAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
2.棧實作綜合電腦
- 請計算表達式:[722-5+1-5+3-3] 的值
- 請問: 計算機底層是如何運算得到結果的? 注意不是簡單的把算式列出運算,因為我們看這個算式 7 * 2 * 2 - 5,但是計算機怎麼了解這個算式的
- 對計算機而言, 它接收到的就是一個字元串, 我們讨論的是這個問題:棧
棧、字尾表達式(逆波蘭表達式)棧
2.字尾表達式
- 字尾表達式又稱逆波蘭表達式,與字首表達式相似,隻是運算符位于操作數之後
- 中綴表達式舉例說明: (3+4)×5-6 對應的字尾表達式就是 3 4 + 5 × 6 –
- 再比如:
正常的表達式 | 逆波蘭表達式 |
---|---|
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.1 逆波蘭電腦
我們完成一個逆波蘭電腦,要求完成如下任務:
- 輸入一個逆波蘭表達式(字尾表達式),使用棧(Stack), 計算其結果
- 支援小括号和多位數整數,因為這裡我們主要講的是資料結構,是以電腦進行簡化,隻支援對整數的計算。
-
思路分析
例如: (3+4)×5-6 對應的字尾表達式就是 3 4 + 5 × 6 - , 針對字尾表達式求值步驟如下:
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,由此得出最終結果
3.2 代碼思路
- 計算字尾表達式無需考慮運算符優先級問題,是以隻需要一個數棧即可
- 分為兩種情況:
- 遇到數:壓入數棧
- 遇到運算符:從數棧中彈出兩個數,進行計算,計算結果壓入數棧
- 何時計算完成?處理完表達式就代表計算完成
代碼實作
- 出棧的兩個數:num2 和 num1
- num2 先出棧,是以 num2 是減數或除數
- num1 後出棧,是以 num1 是被減數或被除數
public class PolandNotation {
public static void main(String[] args) {
//先定義給逆波蘭表達式
// 4 * 5 - 8 + 60 + 8 / 2 => 4 5 * 8 - 60 + 8 2 / +
//說明為了友善,逆波蘭表達式 的數字和符号使用空格隔開
String suffixExpression = "4 5 * 8 - 60 + 8 2 / +"; // 76
//思路
//1. 先将逆波蘭表達式 => 放到ArrayList中
//2. 将 ArrayList 傳遞給一個方法,周遊 ArrayList 配合棧 完成計算
List<String> list = getListString(suffixExpression);
System.out.println("rpnList=" + list);
int res = calculate(list);
System.out.println("計算的結果是=" + res);
}
//将一個逆波蘭表達式, 依次将資料和運算符 放入到 ArrayList中
public static List<String> getListString(String suffixExpression) {
//将 suffixExpression 分割
String[] split = suffixExpression.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>();
// 周遊 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("" + res);
}
}
//最後留在stack中的資料是運算結果
return Integer.parseInt(stack.pop());
}
}
4.中綴表達式轉字尾表達式
4.1舉例說明
- 舉例說明:将中綴表達 式“1+((2+3)×4)-5”轉換為字尾表達式的過程如下
- 是以結果為:“1 2 3 + 4 × + 5 –”
4.2 代碼實作
- 将中綴表達式轉為對應的 List :将數字和運算符分開,存儲在 List 對象中
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]
// 3. 将得到的中綴表達式對應的List => 字尾表達式對應的List
// 即 ArrayList [1,+,(,(,2,+,3,),*,4,),-,5] =》 ArrayList [1,2,3,+,4,*,+,5,–]
String expression = "1+((2+3)*4)-5";// 注意表達式
List<String> infixExpressionList = toInfixExpressionList(expression);
System.out.println("中綴表達式對應的List=" + infixExpressionList); // ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]
List<String> suffixExpreesionList = parseSuffixExpreesionList(infixExpressionList);
System.out.println("字尾表達式對應的List" + suffixExpreesionList); // ArrayList [1,2,3,+,4,*,+,5,–]
System.out.printf("expression=%d", calculate(suffixExpreesionList)); // ?
}
// 方法:将 中綴表達式轉成對應的List
// s="1+((2+3)×4)-5";
public static List<String> toInfixExpressionList(String s) {
// 定義一個List,存放中綴表達式 對應的内容
List<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++; // 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;// 傳回
}
// 即 ArrayList [1,+,(,(,2,+,3,),*,4,),-,5] =》 ArrayList [1,2,3,+,4,*,+,5,–]
// 方法:将得到的中綴表達式對應的List => 字尾表達式對應的List
public static List<String> parseSuffixExpreesionList(List<String> ls) {
// 定義兩個棧
Stack<String> operStack = new Stack<String>(); // 符号棧
// 說明:因為tempList 這個棧,在整個轉換過程中,沒有pop操作,而且後面我們還需要逆序輸出
// 是以比較麻煩,這裡我們就不用 Stack<String> 直接使用 List<String> tempList
// Stack<String> tempStack = new Stack<String>(); // 儲存中間結果的棧tempStack
List<String> tempList = new ArrayList<String>(); // 儲存中間結果的tempList
// 周遊ls
for (String item : ls) {
if (item.matches("\\d+")) { // 如果是一個數,加入tempList
tempList.add(item);
} else if (item.equals("(")) { // 如果是 ( ,則直接入operStack
operStack.push(item);
} else if (item.equals(")")) { // 如果是 ) ,則将括号内的值算出,并壓入 tempList)
// 如果是右括号“)”,則依次彈出operStack棧頂的運算符,并壓入tempList,直到遇到左括号為止,此時将這一對括号丢棄
while (!operStack.peek().equals("(")) {
tempList.add(operStack.pop());
}
operStack.pop();// !!! 将 ( 彈出 s1棧, 消除小括号
} else { // 否則比較目前運算符和棧頂運算符優先級
// 當item的優先級小于等于operStack棧頂運算符,
// 将operStack棧頂的運算符彈出并加入到tempList中,再次轉到(4.1)與operStack中新的棧頂運算符相比較
// 問題:我們缺少一個比較優先級高低的方法
while (operStack.size() != 0 && Operation.getValue(operStack.peek()) >= Operation.getValue(item)) {
tempList.add(operStack.pop());
}
// 還需要将item壓入棧
operStack.push(item);
}
}
// 将operStack中剩餘的運算符依次彈出并加入tempList
while (operStack.size() != 0) {
tempList.add(operStack.pop());
}
return tempList; // 注意因為是存放到List, 是以按順序輸出就是對應的字尾表達式對應的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>();
// 周遊 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("" + res);
}
}
// 最後留在stack中的資料是運算結果
return Integer.parseInt(stack.pop());
}
}
//編寫一個類 Operation 可以傳回一個運算符 對應的優先級
class Operation {
private static int LEFT_BRACKET = 0;
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 = LEFT_BRACKET;
break;
case "+":
result = ADD;
break;
case "-":
result = SUB;
break;
case "*":
result = MUL;
break;
case "/":
result = DIV;
break;
default:
System.out.println("不存在該運算符" + operation);
break;
}
return result;
}
}