1. 棧的概念
棧是一種“操作受限”的線性表,支持兩種基礎操作,入棧和出棧。特點是先進後出,後進先出,也就說是先入棧的數據後出棧,後入棧的數據先出棧。
棧有幾個概念需要我們瞭解:
- 棧大小:就是棧的容量,表示最多可以放多少個數據。
- 棧中元素:棧中的數據
- 棧頂:棧的最上面的元素
- 棧底:棧中最下面的元素
- 入棧:將新的數據放入棧頂
- 出棧:將數據從棧頂取出來
2.實現一個棧
棧可以用數組來實現,也可以用鏈表來實現。用數組實現的棧叫順序棧,用鏈表實現的棧叫鏈式棧。棧只支持兩種操作,入棧和出棧。下面用Python的列表來實現一個棧。
class Stack:
"""
用Python列表實現,其實是一個支持動態擴容的棧。如果是用Java的數組實現的話,需要手動擴容數組。
入棧和出棧時間複雜度是 O(1)
入棧和出棧空間複雜度是 O(1)
"""
def __init__(self):
self._stack = list()
def push(self, ele):
# 不會沒空間,因爲Python的list會動態擴容
self._stack.append(ele)
def pop(self):
if self.is_empty():
return None
return self._stack.pop()
def top(self):
return self._stack[-1]
def min(self):
return min(self._stack)
def max(self):
return max(self._stack)
def is_empty(self):
return self._stack.__len__() == 0
def length(self):
return self._stack.__len__()
def reverse(self):
self._stack = self._stack[::-1]
def clear(self):
while not self.is_empty():
self._stack.pop()
def __repr__(self):
return str(self._stack)
if __name__ == '__main__':
s = Stack()
s.push("a")
s.push(1)
s.push(2)
print(s)
s.reverse()
print(s)
print(s.pop())
print(s.pop())
3.棧的應用場景
3.1 棧在函數調用中的應用
操作系統給每一個線程分配一塊內存空間,這塊內存被組織成棧這種數據結構,用來存儲函數調用時的臨時變量。每進入一個函數,就會將這個函數的臨時變量放入棧中,當函數執行完成後,再把這些臨時變量從棧中去除。比如下面這塊代碼:
int main() {
int a = 1;
int ret = 0;
int res = 0;
ret = add(3, 5);
res = a + ret;
printf("%d", res);
reuturn 0;
}
int add(int x, int y) {
int sum = 0;
sum = x + y;
return sum;
}
a,ret,res這三個是main的臨時變量,x,y,sum是add的臨時變量,main函數調用了add函數。當進入到add函數時,內存的棧數據如下:
當add函數執行完成後,sum,x,y從棧中出棧,當main執行完成後,res,ret,a依次出棧。
3.2 棧在表達式求值中的應用
計算機是如何求表達式3+5*8-6
的值的呢?編譯器是採用兩個棧來實現的,一個棧存放數據的操作數棧,一個棧用來存放操作符的運算符棧。
從左到右遍歷表達式,遇到操作數則放入操作數棧。當遇到運算符,就與運算符棧的棧頂元素進行比較。
如果比運算符棧頂的運算符優先級高,則將運算符放入操作符棧。如果比運算符棧頂的運算符優先級低或者相等,則從操作符棧pop出兩個操作數進行運算,再將結果push到操作數棧。繼續比較。
看下3+5*8-6
的運算過程:
3.3 用棧判斷括號表達式是否合法
我們常見的括號有(),[],{},在使用這些括號時,括號可以嵌套單必須成對出現,比如{[] ()[{}]}或 [{()}([])] 等都爲合法格式,而{[}()] 或 [({)] 爲不合法的格式。
那如何判斷一個包含三種括號的表達式字符串,它的括號用法是否合法呢?用棧就可以方便的解決這個問題。
從左到右掃描字符串,遇到左括號則放入棧,遇到右括號,則與棧頂元素對比,如果匹配則pop掉棧頂的元素,如果匹配,繼續掃描剩下的字符串。
如果掃描過程中,遇到不能匹配的右括號,或者棧中沒有數據,則字符串不合法。
如果掃描完字符串後,棧爲空,則表示字符串合法。否則,說明有未匹配的左括號,字符串非法。
def match_bracket(strings):
open_bracket = "({["
close_bracket = ")]}"
bracket_map = {'(': ')', '[': ']', '{': '}'}
label = True
stack = Stack() # 創建空棧
if strings == "":
return True
for char in strings:
if char not in (open_bracket + close_bracket): # 只處理左右括號
continue
if char in open_bracket:
stack.push(char) # 左括號入棧
continue
if char in close_bracket:
if stack.is_empty(): # 第一個出現的是右括號
label = False
break
if bracket_map[stack.pop()] == char: # 出棧
continue
else:
label = False
break
if not stack.is_empty():
label = False
print(stack)
return label
if __name__ == '__main__':
st = "[([{}][])]"
print(match_bracket(st))
3.4 用棧實現瀏覽器的前進後退功能
瀏覽器的前進、後退功能,大家經常使用。當你依次訪問完一串頁面 a-b-c 之後,點擊瀏覽器的後退按鈕,就可以查看之前瀏覽過的頁面 b 和 a。當你後退到頁面 a,點擊前進按鈕,就可以重新查看頁面 b 和 c。但是,如果你後退到頁面 b 後,點擊了新的頁面 d,那就無法再通過前進、後退功能查看頁面 c 了。
這個功能就可以用棧實現。代碼參考
class Browser:
def __init__(self):
self.forward_stack = Stack() # 前進棧: 保存後退的頁面
self.backward_stack = Stack() # 後退棧: 保存新開頁面或者前進的頁面
def open(self, url):
self.backward_stack.push(url) # 新頁面加入到後退棧
print(f"Open new url: {url}")
self.forward_stack.clear() # 清空前進棧
def back(self):
if not self.backward_stack.is_empty():
top = self.backward_stack.pop() # 從後退棧中彈出
self.forward_stack.push(top)
print(f"Back to {top}")
else:
print("Cannot backward")
def forward(self):
if not self.forward_stack.is_empty():
top = self.forward_stack.pop() # 從前進棧中彈出
self.backward_stack.push(top)
print(f"Forward to {top}")
else:
print("Cannot forward")
if __name__ == '__main__':
browser = Browser()
browser.open('a')
browser.open('b')
browser.open('c')
browser.back()
browser.back()
browser.open('d')
browser.back()
browser.back()
browser.forward()