堆疊(stack)是一種先進後出(First In Last Out, FILO)的資料結構,亦即比較早被放進去的,會比較晚才能被拿出來。生活中最常見的堆疊範例是品※洋芋片的罐子,罐底的那片是最早被放進去的,但是以普通的吃法(不破壞罐子、不搖碎洋芋片、...),且拿出來就當作被吃掉來說,罐底那片會最晚才被吃到。

而對於一罐品※洋芋片,你至少要可以放入一片洋芋片,以及拿出一片最頂端的洋芋片;對應到堆疊的專有名詞,則分別是 push 和 pop。此外,你可能還可以進行其他操作,例如看看最上面那一片、看看罐子裡還有幾片洋芋片、看看罐子裡是否空了、看看罐子是否滿了、...等等;這些操作則沒有規定的名稱,但通常可能會被稱為 top、size、is_empty、is_full、...等等。

堆疊的 push 和 pop,在 Python 當中可以很自然地用 list 的 append 和 pop 來完成:

a = []

a.append(3)
a.append(4)
r = a.pop()
r = a.pop()
r = a.pop() # Leads to error
a.append(567)
r = a.pop()
r = a.pop() # Leads to error

在上述範例中:

用某些其他語言實作堆疊時,或者當你有效能等特殊考量時,可能會先把所需要的空間一次配置完畢,然後多設立一個變數,來代表堆疊現在長到哪裡:

a = [0 for _ in range(3)]
top = 0

# push 3
a[top] = 3
top += 1

# push 4
a[top] = 4
top += 1

# pop
top -= 1
r = a[top]
print(top)

# pop
top -= 1
r = a[top]
print(top)

在上述範例中:

我們把前一個範例包裝成物件導向,並同時解決「一些已知問題」,例如預先配置空間不足,以及堆疊沒東西時的 pop 應該回傳特定值或者噴錯誤:

class Stack:
	def __init__(self, k=10):
		assert k > 0
		self.k = k
		self.a = [None for _ in range(self.k)]
		self.top = 0

	def push(self, v):
		self.a[self.top] = v
		self.top += 1
		if self.top == self.k:
			self.a += [None for _ in range(self.k)]
			self.k += self.k

	def pop(self):
		if self.top == 0:
			return None
		self.top -= 1
		return self.a[self.top]


S = Stack(50)
S.push(3)
S.push(4)
print(S.pop()) # 4
print(S.pop()) # 3
print(S.pop()) # None

在上述範例中:

對於堆疊各項操作的時間複雜度,說明則如下:

操作時間複雜度
Push

假設有無限大的連續空間可使用的理想狀況下,因為就只是在指定位置放個東西而已,而陣列的存取操作是 O(1),因此 push 可以直接當作 O(1)。

考慮到需要配置新空間時,即陣列的大小已達到 k 時,則那次的 push 因為有跑個迴圈,所以是 O(k);但因為每 k 次只會碰到那一次,所以整體平均下來的複雜度還是 O(1)。

Pop 不論堆疊大小是多少,最多都是一樣數量的操作步驟,因此是 O(1)。
Top 此操作是固定依照變數的值去做檢查以及從陣列取值,而陣列取出任何一個地方的值都是 O(1),所以 top 的操作是 O(1)。當然你如果每次都故意從陣列的開頭開始掃,那就會變成 O(n)。
Size 可以單純透過檢查變數值來進行,因此是 O(1)。
Is_empty
Is_full 前面的範例會持續的配置空間,並沒有另外設置上限(把記憶體用滿的狀況,不在本課程討論範圍內),因此此操作無意義。而如果你設定了上限,並在空間配置時最多只配到此上限,則 is_full 的操作因為只需要單純的檢查變數,因此是 O(1)。