数据结构基础:栈(Stack)
什么是棧?
? ? 棧是限制插入和刪除只能在同一個位置上進行的表,這個位置就是棧的頂端,對于棧的操作主要有三種形式:入棧(將元素插入到表中),出棧(將表最后的元素刪除,也就是棧頂的元素),返回棧頂元素。棧有時又叫LIFO(后進先出)表。
棧的實現
棧可以有兩種實現方式:1)數組實現 2)鏈表實現
棧的雙向鏈表實現
public class StackForLinked<T>{private int num = 0;public int count { get{return num;} }private Node<T> root = null;private class Node<T>{public T data; public Node<T> prev;public Node<T> next;public Node(T value){this.data = value;this.prev=null;this.next=null;} public Node(T value,Node<T> prev,Node<T> next):this(value){this.prev = prev;this.next=next;}}public void Push(T value){Node<T> node = new Node<T>(value);if(root==null){root = node;}else{var last= GetLastNode();last.next = node;node.prev= last;}num++;}public T Pop(){var node=GetLastNode();if(node==null)throw new Exception();node.prev.next=null;num--;return node.data;}public T GetTop(){var node =GetLastNode();if(node==null)throw new Exception();return node.data;}private Node<T> GetLastNode(){Node<T> p = root;if(root==null){return null;}while(true){if(p.next!=null){p=p.next;}else break;}return p;}}棧的數組實現
public class StackForArray<T>{private static int length = 100;private T[] objList = new T[length]; private int currentIndex = 0;public int count { get { return this.currentIndex;} }private void ensureCapcity(int capcity){if(capcity < currentIndex){return;}T[] old = objList;objList = new T[capcity];for(int i=0;i<old.Length;i++){objList[i] = old[i];}}public void Push(T value){if(currentIndex == length){ensureCapcity(currentIndex * 2 +1);}objList[currentIndex] = value;currentIndex++;}public T Pop(){if(currentIndex == 0)throw new Exception();currentIndex--;return objList[currentIndex];}public T GetTop(){if(currentIndex == 0){throw new Exception();}return objList[currentIndex -1];}}PS:對于鏈表在添加和刪除方面是O(1), 但是在查找方面則是O(N);數組在查找方面是O(1)操作,但是在插入或者是刪除方面涉及到表的中元素的位置上的移動,若數據中的元素較多會帶來性能上的損失。所以在考慮使用鏈表還是使用數組進行存儲數據的時候,應該考慮上述所說(數據量大小,插入和刪除多還是查找的多)。當前這與棧的關系不太大,因為棧都是在一端進行操作,不會涉及元素移動的問題。
棧的應用
棧的應用有很多,這里只舉一些簡單的例子:
1.平衡符號:例如判斷括號是否成對出現,通過將括號入棧然后碰見成對的括號就出棧,最后判斷棧中的元素是否為空,為空則是成對出現,反之則不成對
2.中綴表達式轉化為后綴表達式:
? ? 中綴表達式和后綴表達式: 例如表達式 a + b c 這就是我們平時所見的正常的數學計算表達式,在數學上我們也知道怎么計算,但是你把這一段表達式告訴計算機,然后計算出結果,那么這個結果是怎么計算出來的呢?這里就涉及一個概念叫做后綴表達式,首先 a + b c 會先通過棧轉化為 a b c * + 然后再通過棧進行計算從而得出結果,前者通常叫做中綴表達式。隨后會有一段代碼的例子,可以讓你真正的了解數學表達式是如何通過棧進行計算的
3.方法調用:類似于平衡符號的檢測,所有的方法調用和返回都是成對的,在開始調用的時候需要在棧內存儲一些信息(位置,對應變量的名字和返回地址等),方法完成后需要從棧中出棧拿到當初存儲的信息然后做一些操作,這也正好印證了,當遞歸調用的時候,不當的遞歸會出現 Stackoverflow 異常,也就是說在棧(在window下,默認大小是1M )中已經存儲不下每一次遞歸的信息了,就會報這個錯誤。
PS: 我這里說的都是同步方法,存在一個方法的調用需要等待另一個方法的完成,異步方法還不太了解,期待后續學習會有更多的收獲
棧實現基本操作的運算
? ? 用棧來實現簡單的基本數學運算:+ ,- ,* , / 和( )在數學中我們都知道這幾種符號的優先級,那么計算機時怎么做到的呢? 下面我們以一個表達式為例子來實現一下計算機的計算過程
? ? 表達式:a + b c + ( d e + f ) * g ,除棧外,我們還需要一個輸出字符串用來存儲生成的后綴表達式,默認情況下為空,出于方便我會使用 [] 代表一個棧,[ 代表棧底,] 代表棧頂,用 | 分割每個元素。
原理
1.對常數輸出,對操作符號入棧操作,根據優先級別判斷是出棧還是入棧;
2.優先級低的不允許出現在優先級高的后面(棧中存在一個優先級高的符號,此時來了一個低優先級符號,需要進行先把高優先級的符號出棧,然后將低優先級的符號入棧);
3.同優先級的符號先出棧再入棧;
4.對于存在括號的情況,當前棧頂的元素是 ( 那么接下來的一個符號不比較優先級直接入棧,接下來的操作遵循前3條規則;
5.當遇見 )的時候,執行出棧操作直到遇見出棧的第一個( 為止;
6.得到后綴表達式之后再次使用棧進行計算,將后綴表達式中的值入棧,碰見操作符,出棧兩個元素執行運算符計算,將計算的結果插入到棧中,直到后綴表達式的盡頭,最終結果位于棧頂,出棧即可得到
步驟:
1.將中綴表達式轉化為后綴表達式
Step 1 : 我們第一次讀取的是常數 a ,直接輸出,此時輸出的字符串中的值為 a
Step 2 : 繼續讀取,接下來讀取的是操作符號 + ,入棧操作,此時棧中的元素為 [ + ]
Step 3 : 繼續讀取,接下來讀取的是常數 b , 直接輸出,此時輸出的字符串中的值為 a b
Step 4 : 繼續讀取,接下來讀取的是操作符號 , 入棧操作,此時棧中的元素為 [ + | ]
Step 5 : 繼續讀取,接下來讀取的是常數 c , 直接輸出,此時輸出的字符串中的值為 a b c
Step 6 : 繼續讀取,接下來讀取的是操作符號 + , 棧頂元素 的優先級高于 + ,將 出棧,之后發現棧頂元素為 +,優先級相同,繼續出棧,然后將 + 進棧,此時棧中的元素為[ + ],輸出字符串中的值為 a b c * +
Step 7 : 繼續讀取,接下來讀取的是操作符號 ( , 入棧操作,此時棧中的元素為 [ + | ( ]
Step 8 : 繼續讀取,接下來讀取的是常數 d , 直接輸出,此時輸出的字符串中的值為 a b c * + d
Step 9 : 繼續讀取,接下來讀取的是操作符號 , 入棧操作,此時棧中的元素為[ + | ( | ]
Step 10 : 繼續讀取,接下來讀取的是常數 e , 直接輸出,此時輸出的字符串中的值為 a b c * + d e
Step 11 : 繼續讀取,接下來讀取的是操作符號 + , 棧頂元素 的優先級高于 + ,先執行出棧操作,在執行 + 入棧操作,此時棧中的元素為[ + | ( | +] , 輸出字符串中的值為 a b c + d e *
Step 12 : 繼續讀取,接下來讀取的是常數 f , 直接輸出,此時輸出的字符串中的值為 a b c + d e f
Step 13 : 繼續讀取,接下來讀取的是操作符號 ) , 開始執行出棧操作,直到遇見第一個出棧的元素為 ( 停止,此時棧中的元素為[ + ] , 輸出字符串中的值為 a b c + d e +
Step 14 : 繼續讀取,接下來讀取的是操作符號 , 入棧操作,此時棧中的元素為[ + | ]
Step 15 : 繼續讀取,接下來讀取的是常數 g , 直接輸出,此時輸出的字符串中的值為 a b c + d e f + g
Step 16: 繼續讀取,到達了末端,開始執行中出棧操作,直到棧為空,此時輸出的字符串中的值為 a b c + d e f + g + ,從而得出后綴表達式為:a b c + d e f + g +
2.計算后綴表達式 (Step中的括號的使用是起到輔助清晰的作用,因為并沒給字符實際的值)
Step 1 : 讀取后綴表達式,第一個遇到的是 a 執行入棧操作,此時棧中的元素為 [ a ]
Step 2 : 繼續讀取,解下來遇到的是 b ,執行入棧操作,此時棧中的元素為 [ a | b ]
Step 3 : 繼續讀取,解下來遇到的是 c ,執行入棧操作,此時棧中的元素為 [ a | b | c ]
Step 4 : 繼續讀取,解下來遇到的是 ,出棧兩個元素,計算 b c ,然后在執行入棧, 此時棧中的元素為 [ a | b * c ]
Step 5 : 繼續讀取,解下來遇到的是 + ,出棧兩個元素,計算 a + b c,然后執行入棧操作,此時棧中的元素為 [ a + b c ]
Step 6 : 繼續讀取,解下來遇到的是 d ,執行入棧操作,此時棧中的元素為 [ a + b * c | d ]
Step 7 : 繼續讀取,解下來遇到的是 e ,執行入棧操作,此時棧中的元素為 [ a + b * c | d | e ]
Step 8 : 繼續讀取,解下來遇到的是 ,出棧兩個元素,計算 d e ,然后執行入棧操作,此時棧中的元素為 [ a + b c | d e ]
Step 9 : 繼續讀取,解下來遇到的是 f ,執行入棧操作,此時棧中的元素為 [ a + b c | d e | f ]
Step 10 : 繼續讀取,解下來遇到的是 + ,出棧兩個元素,計算 d e + f ,然后執行入棧操作,此時棧中的元素為 [ a + b c | d * e + f ]
Step 11 : 繼續讀取,解下來遇到的是 g ,執行入棧操作,此時棧中的元素為 [ a + b c | d e + f | g ]
Step 12 : 繼續讀取,解下來遇到的是 ,出棧兩個元素,計算 ( d e + f ) g ,然后執行入棧操作,此時棧中的元素為 [ a + b c | ( d e + f ) g ]
Step 13 : 繼續讀取,解下來遇到的是 + ,出棧兩個元素,計算 a + b c + ( d e + f ) g ,然后執行入棧操作,此時棧中的元素為 [ a + b c + ( d e + f ) g ]
Step 14 : 繼續讀取,到達了末端,執行出棧操作,出棧的結果就是計算的結果 a + b c + ( d e + f ) * g
實現代碼
class Program {static List<Priority> priority = new List<Priority>(){new Priority() { level = 0 , symbal ="+" },new Priority() { level = 0 , symbal ="-" },new Priority() { level = 1 , symbal ="*" },new Priority() { level = 1 , symbal ="/" },new Priority() { level = int.MaxValue , symbal ="(" },new Priority() { level = int.MaxValue , symbal =")" }};static void Main(string[] args){//string expression = "a + b * c + ( d * e + f ) * g";//string expression ="10 + 1 * 10 + ( 2 * 3 + 4 ) * 10"; // 120 //string expression ="1 + 2 + 3 ";string expression ="3 * ( ( 2 - 4 ) / ( 2 - 1 ) ) ";GenerateExpression(expression);Console.WriteLine(OutputStr);Console.WriteLine(CalculateValue());}///生成后綴表達式的棧static StackForArray<Priority> symbalStack = new StackForArray<Priority>();/// 輸出字符串static string OutputStr=string.Empty;/// 生成后綴表達式static void GenerateExpression(string expression){string[] symbals = expression.Split(' ');foreach(string symbal in symbals){RecursionExpression(symbal);}while(true){try{var popElement = symbalStack.Pop();OutputStr += popElement.symbal +" ";}catch(Exception ex){break;}}}// 計算表達式的值static int CalculateValue(){StackForArray<int> valueStack = new StackForArray<int>();string[] symbals = OutputStr.Split(new char[]{' '},StringSplitOptions.RemoveEmptyEntries);foreach(var symbal in symbals){var temp = priority.Where(x=>x.symbal ==symbal).FirstOrDefault();if(temp!=null){var t = 0;var second = valueStack.Pop();var first = valueStack.Pop();switch (symbal){case "*":t = first * second;break;case "/":t = first / second;break;case "+":t = first + second;break;case "-":t = first - second;break;default:throw new Exception();}valueStack.Push(t);}else{valueStack.Push(int.Parse(symbal));}}return valueStack.Pop();}/// 遞歸表達式static void RecursionExpression(string symbal){var temp = priority.Where(x=>x.symbal == symbal).FirstOrDefault();if(temp==null) {OutputStr+= symbal + " ";return;}var stackCount = symbalStack.count;if(stackCount==0 || temp.symbal =="("){symbalStack.Push(temp);return;}if(temp.symbal ==")"){while(true){var popElement = symbalStack.Pop();OutputStr += popElement.symbal +" ";if(popElement.symbal=="("){OutputStr=OutputStr.Substring(0,OutputStr.Length -2);break;}}return;}var topElement = symbalStack.GetTop();if(temp.level > topElement.level || topElement.symbal=="("){symbalStack.Push(temp);return;}if(temp.level <= topElement.level && topElement.symbal != "("){var popElement= symbalStack.Pop();OutputStr += popElement.symbal +" ";RecursionExpression(temp.symbal);return;}} }public class Priority{public string symbal { get; set; }public int level { get; set; }} }PS:測試的代碼比較簡單,主要看思路吧
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的数据结构基础:栈(Stack)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: lintcode 418整数转罗马数字
- 下一篇: 【深入JAVA】java注解