棧 是一種 “操作受限”的線性表,只允許在一端插入和刪除數據。
從功能是上來說,數組和鏈表確實可以替代棧,但是特定的數據結構是對特定場景的抽象,而且,數組或鏈表暴露了太多的操作接口,操作上的確靈活自由,但使用時就比較不可控,自然也就更容易出錯。
當某個數據集合只涉及在一端插入和刪除數據,并且滿足后進先出、先進后出的特性,這時我們就應該首選“棧”這種數據結構。
棧的應用:
函數調用,操作系統給每個線程分配了一塊獨立的內存空間,這個內存被組織成“棧”這種結構,用來存儲函數調用時的臨時變量。每進入一個函數,就會將臨時變量作為一個棧幀入棧,當被調用函數執行完成,返回之后,將這個函數對應的棧幀出棧。
表達式求值
比如 3 + 5 * 8 - 6,編譯器通過兩個棧來實現,其中一個保存操作數的棧,另一個是保存運算符的棧,從左向右遍歷表達式,當遇到數字,我們就直接壓入操作數棧;當遇到運算符,就與運算符棧的棧頂元素進行比較。
如果比運算符棧頂元素的優先級高,就將當前運算符壓入棧;如果比運算符棧頂元素的優先級低或者相同,從運算符棧中取棧頂運算符,從操作數棧的棧頂取2個操作數,然后進行計算,再把計算完的結果壓入操作數棧,繼續比較。
image.jpeg
在括號匹配中的應用
假設表達式中只包含三種括號,圓括號()、方括號[]和花括號{},并且他們可以任意套裝。比如,{[{}]}為合法格式,而 {[}()] 或 [({)]}為不合法格式,現在檢查其合法性。
用棧來保存未匹配的左括號,從左到右依次掃描字符串。當掃描到左括號時,則將其壓入棧中;當掃描到右括號時,從棧頂取出一個左括號,如果能夠匹配,比如 ) 跟 ( 匹配, [ 跟 ] 匹配,則繼續掃描剩下的字符串,如果在掃描的過程中,遇到不能配對的右括號,或者棧中沒有數據,則說明為非法格式。
當所有的括號都掃描完成之后,如果棧為空,則說明字符串為合法格式;否則,說明有未匹配的左括號,為非法格式。
瀏覽器的前進和后退功能
使用兩個棧,X 和 Y, 把首次瀏覽的頁面一次壓入棧 X, 當點擊后退按鈕時,再一次從棧 X 中出棧,并將出棧的數據一次放入棧 Y。 當我們點擊前進按鈕時,依次從棧 Y 中取出數據,放入棧 X 中。當棧 X 中沒有數據時,那就說明沒有頁面可以繼續后退瀏覽了,當棧 Y 中沒有數據時,說明沒有頁面可以點擊前進按鈕瀏覽了。
比如,順序查看了 a, b, c 三個頁面,依次把 a, b, c 壓入棧,這個時候,兩個棧的數據就是這個樣子:
image.jpeg
當通過瀏覽器的后退按鈕,從頁面 c 后退到頁面 a 之后,就依次把 c 和 b 從棧 X 中彈出,并且依次放入到棧 Y。 這個時候,兩個棧的數據就是這個樣子:
image.jpeg
這個時候,如果又想看頁面b,于是又點擊前進按鈕回到b頁面,就把b從棧Y中出棧,放入棧X中,此時,兩個棧的數據就是這個樣子:
image.jpeg
這個時候,通過頁面b又跳轉到新的頁面d了,頁面 c 就無法再通過 前進、后退按鈕重復查看了,所以需要清空棧 Y。此時兩個棧的數據是這個樣子:
image.jpeg
總的來說,棧是一種操作受限的數據結構,只支持入棧和出棧操作,后進先出是它最大的特點。棧既可以通過數組實現,也可以通過鏈表來實現。不管是基于數組還是鏈表,入棧、出棧的時間復雜度都為 O(1)。