[從今天開始修煉資料結構]棧、斐波那契數列、逆波蘭四則運算的實現
一、棧的定義
棧是限定僅在表尾進行插入和刪除操作的線性表。允許插入和刪除的一端稱為棧頂(top),另一端稱為棧底(bottom)。棧又稱後進先出的線性表,簡稱LIFO結構。
注意:首先它是一個線性表,也就是說棧元素有前驅後繼關係。
棧的插入操作,叫做進棧,也稱壓棧、入棧
棧的刪除操作,叫做出棧,也叫彈棧。
注意:最先入棧,不代表就要最後出棧。因為棧沒有限制出棧的時間,例如可以先入棧兩個元素,再出棧兩個元素,後入棧其他元素。
二、棧的抽象資料型別
ADT Stack Data 同線性表。元素具有相同的型別,相鄰元素具有前驅和後繼。 Operation InitStack(S) : 初始化操作,建立一個空棧 DestroyStack(S):若棧存在,銷燬它。 ClearStack(S):將棧清空 StackEmpty:若棧為空,返回true,否則返回false。 GetTop(S,e):若棧存在且非空,用e返回棧頂元素。 Push(S,e):若棧S存在,插入新元素e到棧頂 Pop(S,e):刪除S中棧頂元素,並用e返回其值 StackLength(S):返回棧S中的元素個數 endADT
三、棧的順序結構
1,棧的順序儲存結構是什麼樣子
棧的順序儲存結構也是用陣列來實現的。讓下標0的一端作為棧底,另一端作為棧頂。若儲存棧的長度為StackSize,則棧頂位置top必須小於StackSize。當棧存在一個元素時,top = 0.因此通常把空棧的判定條件定為top = - 1。
2,順序結構棧的實現
package Stack; public class ArrayStack<T>{ private Object[] list; private int top; private int size; private static int DEFAULT_SIZE = 10; public ArrayStack(int size){ list = new Object[size]; this.size = size; top = -1; } public ArrayStack(){ this(DEFAULT_SIZE); } public void push(T data){ if (!isFull()){ list[++top] = data; }else { System.out.println("棧滿了"); } } public T pop(){ if (!isEmpty()){ T obj = (T)list[top]; list[top] = null; top--; return obj; }else { System.out.println("棧是空的"); } return null; } private boolean isEmpty(){ return top == -1; } private boolean isFull() { return top >= size; } public int size(){ return size; } public int getTop(){ return top; } }
四、共享棧
1,共享棧是指兩棧共享一片空間的棧。如下圖所示
兩個棧共用一塊陣列,以兩頭為棧底,中間為棧頂。可以節省空間。判斷滿的方法是top1 + 1 = top2.
2,共享棧的實現。
package Stack; public class ShareStack<T> { private Object[] list; private int top1; private int top2; private int capacity; private static int DEFAULT_SIZE = 10; //private boolean isFull = false; public ShareStack(int capacity){ list = new Object[capacity]; this.capacity = capacity; top1 = -1; top2 = capacity; } public ShareStack(){ this(DEFAULT_SIZE); } public boolean pushStackOne(T data){ if (!isFull()){ list[++top1] = data; //isFull = isFull(); return true; }else{ System.out.print("棧滿了!"); return false; } } public boolean pushStackTwo(T data){ if (!isFull()){ list[--top2] = data; //isFull = isFull(); return true; }else{ System.out.print("棧滿了!"); return false; } } public T popStackOne(){ if (!isEmptyOne()){ T data = (T)list[top1--]; return data; }else { System.out.println("棧一是空的!"); return null; } } public T popStackTwo(){ if (!isEmptyTwo()){ T data = (T)list[top2++]; return data; }else { System.out.println("棧二是空的!"); return null; } } private boolean isEmptyTwo() { return top2 == capacity; } private boolean isFull() { return top1 == (top2 - 1); } private boolean isEmptyOne(){ return top1 == -1; } }
五、棧的鏈式儲存及實現
1,棧的鏈式儲存簡稱鏈棧,把棧頂放在單鏈表的頭部。如下圖
2,鏈棧的實現
public class LinkedStack<T> { private Node<T> top; public void push(T data){ Node<T> newNode = new Node<>(data, null); Node<T> t = top; top = newNode; top.next = t; } public T pop(){ Node<T> t = top; if (!isEmpty()){ top = top.next; return t.data; }else { System.out.println("棧為空!"); return null; } } private boolean isEmpty() { return top == null; } private class Node<T> { private T data; private Node next; public Node(T data, Node<T> next){ this.data = data; this.next = next; } } }
以表頭為棧頂,降低了彈棧操作的時間複雜度。若以表尾為棧頂則彈棧只能遍歷找到top-1位置,或者用雙向連結串列犧牲空間。如下
public T pop(){ if (!isEmpty()){ Node<T> newTop = bottom; while (newTop.next != top){ newTop = newTop.next; } T data = top.data; top = newTop; top.next = null; return data; }else { System.out.println("棧是空的!"); return null; } }
五、棧的應用 —— 遞迴
1,斐波那契數列的遞迴實現
引例:如果兔子在出生兩個月後就有繁殖能力,一對兔子每個月能生出一對小兔子。假設所有兔子都不死,那麼一年後有多少對兔子?分析得出兔子數量按天數增加如下表
所經過的月數 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
兔子對數 | 1 | 1 | 2 | 3 | 5 | 8 | 13 | 21 | 34 | 55 | 89 | 144 |
表中數列有明顯的特點:相鄰前兩項之和等於後一項。抽象成函式表達如下
迭代實現如下:
public class Fibonacci {
public static void main(String[] args) throws Exception {
System.out.println(Fibonacci(12));
}
public static int Fibonacci(int month) throws Exception {
if (month < 0){
throw new Exception();
}
if (month < 2){
return month == 0 ? 0 : 1;
}else {
return (Fibonacci(month - 1) + Fibonacci(month - 2));
}
}
}
程式碼執行過程如下:
遞迴實現法的時間複雜度為O(2n),太高,還可能會引起記憶體棧溢位,所以應該用別的方法求解。這篇文章主要講由棧引出遞迴,所以先挖個坑,在後面的文章中我會單獨討論遞迴和斐波那契數列的其他解法。
參考:https://blog.csdn.net/sofia_m/article/details/78796084
https://blog.csdn.net/IronWring_Fly/article/details/100050016
https://blog.csdn.net/Bob__yuan/article/details/84956740
2,在前行階段,對於每一層遞迴,函式的區域性變數、引數值以及返回地址都被壓入棧中。在退回階段,位於棧頂的區域性變數、引數值和返回地址被彈出,用於返回呼叫層次中執行程式碼的其餘部分,也就是恢復了呼叫的狀態。
六、棧的應用 —— 四則運算表示式求值
1,字尾(逆波蘭)表示法定義
對於會計計算器,不能表達複雜的帶括號和多種運算子號複合的表示式;而對於科學計算器,我們可以一次將一個複雜的帶括號的四則運算輸入進去,計算器是怎麼做到的呢?
這就引入了一種不需要括號的字尾表達法 —— 逆波蘭表達法。舉個例子來看 對於 “ 9 + ( 3 - 1) * 3 + 10 / 2”這個表示式,轉換成字尾表示式應該變為 “ 9 3 1 - 3 * + 10 2 / + ”。 叫做字尾的原因就是所有的符號都在被運算的數字後面出現。
2,我們先來看看字尾表示式是如何計算的
規則: 從左到右遍歷表示式的每個數字和符號,遇到數字就進棧,遇到符號,就把數字棧棧頂的兩個數字出棧,棧頂元素放在運算子後面,棧頂下面一個元素放在運算子前面。再將運算結果進棧,如此往復,直到獲得最終結果。
步驟精解:我們以上面的 “ 9 3 1 - 3 * + 10 2 / + ” 為例
(1)初始化一個空棧,用來對要運算的數字進行進出使用
(2)字尾表示式中前三個都是數字,所以將9 3 1 依次進棧。
(3)接下來是運算子“ - ”,所以將1, 3 出棧,運算3 - 1得到2 ,將2進棧,然後遇到3 ,將3進棧
(4)後面遇到“ * ”,將3, 2出棧,計算2 * 3,得到6,將6進棧
(5)遇到“ + ”,將6 , 9出棧,計算9 + 6,得到15,將15進棧
(6)將10, 2 進棧
(7)遇到符號“ / ” 將2, 10出棧,計算10/2得到5,將5壓棧
(8)最後一個符號 “ + ”,將15,5出棧,計算15 + 5,得到20,將20壓棧
(9)表示式讀取完成,將結果20出棧,棧變為空。
這就是通過後綴表示式計算四則運算的結果。那麼下面我們再來討論如何將中綴表示式(也就是我們平時數學課上計算的形式)轉換為方便計算機運算的字尾表示式呢?
3,中綴轉字尾
規則:從左到右遍歷中綴表示式,如果是數字就存入字尾表示式,如果是符號就判斷其與棧頂符號的優先順序,如果優先順序不高於棧頂符號,則棧頂元素依次出棧並輸出,並將當前符號進棧。特殊的,遇到左括號就進棧,左括號的優先順序不做判斷;當遇到右括號時,將與其匹配的左括號以上的全部符號依次出棧。直到最終輸出字尾表示式為止。
具體步驟:我們還是以上面的 “ 9 + ( 3 - 1) * 3 + 10 / 2”這個表示式為例
(1)初始化一個空棧。遇到第一個字元是數字9,輸出9,遇到符號“ + ”,進棧。
(2)遇到字元“ ( ”進棧,然後遇到了數字3,輸出,又遇到了符號“ - ”,進棧。如下圖
(3)遇到符號“ ) ”,根據規則,匹配前面的“ ( ”,並將其上的符號依次出棧並輸出,直到“ ( ”出棧。
(4)下面遇到了符號“ * ”,比較其與棧頂元素 “ + ”的優先順序。* 的優先順序更高,所以壓棧。 後面遇到了數字3 ,輸出。
(5)之後遇到了符號 “ + ”,與棧頂元素“ * ”比較, + 的優先順序低,所以將 * 彈棧,再與現在的棧頂 + 比較,與新來的 + 優先順序相同,所以將棧頂+也出棧,將新來的 + 彈棧。如下圖
(6)緊接著數字10,輸出,後面是符號“ / ”,進棧。 最後一個數字2 輸出。原表示式讀取完成,將棧內剩餘的符號都出棧,得到
4,實現逆波蘭四則運算(靜態方法呼叫)
package Stack; import java.util.Stack; import java.util.regex.Pattern; public class RPNmethod { private static Stack<String> charStack = new Stack<>(); private static Stack<Integer> intStack = new Stack<>(); public static int RPN(String exp){ String behindEXP = convert(exp); String[] chars = behindEXP.split(" "); for (int i = 0; i < chars.length; i++){ String thisOne = chars[i]; if (isNum(thisOne)){ intStack.push(Integer.parseInt(thisOne)); }else if (isSymbol(thisOne)){ int a = intStack.pop(); int b = intStack.pop(); int result; switch (thisOne){ case "+": result = b + a; intStack.push(result); break; case "-": result = b - a; intStack.push(result); break; case "*": result = b * a; intStack.push(result); break; case "/": result = b / a; intStack.push(result); break; } } } return intStack.pop(); } /** * 將中綴表示式轉為字尾表示式 * @param middle * @return */ private static String convert(String middle){ String rpnEXP = ""; String[] chars = middle.split(" "); for(int i = 0; i < chars.length; i++){ String thisOne = chars[i]; if (isNum(thisOne)){ rpnEXP = rpnEXP + thisOne + " "; }else if (isSymbol(thisOne)) { /* 三個分支 : 是左括號,是右括號,不是括號 */ if (isLeftPar(thisOne)) { charStack.push(thisOne); } else if (isRightPar(thisOne)) {//Here while (!isLeftPar(thisOne)) { thisOne = charStack.pop(); if (!isLeftPar(thisOne)) { rpnEXP = rpnEXP + thisOne + " "; } } } else { if (charStack.isEmpty() || !lowerPriority(thisOne, charStack.peek())) { charStack.push(thisOne); } else { do { rpnEXP = rpnEXP + charStack.pop() + " "; } while (!charStack.isEmpty() && lowerPriority(thisOne, charStack.peek())); charStack.push(thisOne); } } } } while(!charStack.isEmpty()){ rpnEXP = rpnEXP + charStack.pop() + " "; } return rpnEXP; } private static boolean isLeftPar(String aChar) { return aChar.equals("("); } /** * 返回新來的優先順序是不是不大於棧頂.aChar > peek 就返回false,peek >= aChar 就返回true * @param aChar * @param peek * @return 返回true就彈棧,返回false就將新來的壓棧 */ private static boolean lowerPriority(String aChar, String peek) { if(peek.equals("(")){ return false; } else if (aChar.equals("+") || aChar.equals("-")){ return true; }else { if (peek.equals("*") || peek.equals("/")){ return true; }else { return false; } } } private static boolean isRightPar(String c) { return c.equals(")"); } private static boolean isNum(String c){ Pattern pattern = Pattern.compile("[\\d]*$"); return pattern.matcher(c).matches(); } private static boolean isSymbol(String c){ String pattern = "[-/*+()]"; return Pattern.matches(pattern,c) ; } }
&n