Stacks (LIFOs)
A stack is a collection of objects that supports fast last-in, first-out (LIFO) semantics for inserts and deletes. Unlike lists or arrays, stacks typically don’t allow for random access to the objects they contain. The insert and delete operations are also often called push and pop.
堆棧是支持后進先出的插入和刪除操作的對象集合。不像列表和數組,堆棧一般上不能允許隨機訪問堆棧中含有的對象。插入和刪除操作也被成為壓入和彈出。
A useful real-world analogy for a stack data structure is a stack of plates:
New plates are added to the top of the stack. And because the plates are precious and heavy, only the topmost plate can be moved (last-in, first-out). To reach the plates that are lower down in the stack, the topmost plates must be removed one by one.
Stacks and queues are similar. They’re both linear collections of items, and the difference lies in the order that the items are accessed:
堆棧和隊列是類似的。它們都是線性物品集合。它們兩的區(qū)別就是item訪問的順序不一樣。
With a queue, you remove the item least recently added (first-in, firstout or FIFO); but with a stack, you remove the item most recently added (last-in, first-out or LIFO).
隊列中是先進先出,堆棧是后進先出。
Performance-wise, a proper stack implementation is expected to take O(1) time for insert and delete operations.
堆棧操作插入和刪除操作時間復雜度為O(1)。
Stacks have a wide range of uses in algorithms, for example, in language parsing and runtime memory management (“call stack”). A short and beautiful algorithm using a stack is depth-first search (DFS) on a tree or graph data structure.
Python ships with several stack implementations that each have slightly different characteristics. We’ll now take a look at them and compare their characteristics.
list – Simple, Built-In Stacks
Python’s built-in list type makes a decent stack data structure as it supports push and pop operations in amortized O(1) time.
Python’s lists are implemented as dynamic arrays internally, which means they occasionally need to resize the storage space for elements stored in them when elements are added or removed. The list overallocates its backing storage so that not every push or pop requires resizing, and as a result, you get an amortized O(1) time complexity for these operations.
就是是列表操作的時候刪改和插入的時候需要重新調整存儲空間。
The downside is that this makes their performance less consistent than the stable O(1) inserts and deletes provided by a linked list based implementation (like collections.deque, see below). On the other hand, lists do provide fast O(1) time random access to elements on the stack, and this can be an added benefit.
Here’s an important performance caveat you should be aware of when using lists as stacks:
提醒有缺點要注意了。
To get the amortized O(1) performance for inserts and deletes, new items must be added to the end of the list with the append() method and removed again from the end using pop(). For optimum performance, stacks based on Python lists should grow towards higher indexes and shrink towards lower ones.
為了獲得最佳性能,基于python列表的堆棧應該朝著更高的索引增長,并朝著更低的索引收縮。
Adding and removing from the front is much slower and takes O(n) time, as the existing elements must be shifted around to make room for the new element. This is a performance antipattern that you should avoid as much as possible.
添加和刪除前面的元素變慢了,因為需要為了這個操作騰出空間給新的元素。這種操作我們應該盡量避免。
>>> s = []
>>> s.append('eat')
>>> s.append('sleep')
>>> s.append('code')
>>> s
['eat', 'sleep', 'code']
>>> s.pop()
'code'
>>> s.pop()
'sleep'
>>> s.pop()
'eat'
>>> s.pop()
IndexError: "pop from empty list"
collections.deque – Fast & Robust Stacks
The deque class implements a double-ended queue that supports adding and removing elements from either end in O(1) time (nonamortized). Because deques support adding and removing elements from either end equally well, they can serve both as queues and as stacks.
Python’s deque objects are implemented as doubly-linked lists which gives them excellent and consistent performance for inserting and deleting elements, but poor O(n) performance for randomly accessing elements in the middle of a stack.
deque操作從兩端開始的插入和刪除操作都是效果不錯的,但是在中部的元素的插入和刪除相對表現差一些。
Overall, collections.deque is a great choice if you’re looking for a stack data structure in Python’s standard library that has the performance characteristics of a linked-list implementation.
總的來說,如果您在Python的標準庫中尋找具有鏈表實現性能特征的堆棧數據結構,collections.deque是一個很好的選擇。
>>> from collections import deque
>>> s = deque()
>>> s.append('eat')
>>> s.append('sleep')
>>> s.append('code')
>>> s
deque(['eat', 'sleep', 'code'])
>>> s.pop()
'code'
>>> s.pop()
'sleep'
>>> s.pop()
'eat'
>>> s.pop()
IndexError: "pop from an empty deque"
queue.LifoQueue – Locking Semantics for Parallel Computing
This stack implementation in the Python standard library is synchronized and provides locking semantics to support multiple concurrent producers and consumers.
python標準庫中的這個堆棧實現是同步的并提供鎖語義以支持多個并發(fā)。
意思就是這個支持并發(fā)操作。
Besides LifoQueue, the queue module contains several other classes that implement multi-producer/multi-consumer queues that are useful for parallel computing.
并行計算。
Depending on your use case, the locking semantics might be helpful, or they might just incur unneeded overhead. In this case you’d be better off with using a list or a deque as a general-purpose stack.
>>> from queue import LifoQueue
>>> s = LifoQueue()
>>> s.put('eat')
>>> s.put('sleep')
>>> s.put('code')
>>> s
<queue.LifoQueue object at 0x108298dd8>
>>> s.get()
'code'
>>> s.get()
'sleep'
>>> s.get()
'eat'
>>> s.get_nowait()
queue.Empty
>>> s.get()
# Blocks / waits forever...
Comparing Stack Implementations in Python
As you’ve seen, Python ships with several implementations for a stack data structure. All of them have slightly different characteristics, as well as performance and usage trade-offs.
If you’re not looking for parallel processing support (or don’t want to handle locking and unlocking manually), your choice comes down to the built-in list type or collections.deque. The difference lies in the data structure used behind the scenes and overall ease of use:
list is backed by a dynamic array which makes it great for fast random access, but requires occasional resizing when elements are added or removed. The list over-allocates its backing storage so that not every push or pop requires resizing, and you get an amortized O(1) time complexity for these operations. But you do need to be careful to only insert and remove items “from the right side” using append() and pop(). Otherwise, performance slows down to O(n).
collections.deque is backed by a doubly-linked list which optimizes appends and deletes at both ends and provides consistent O(1) performance for these operations. Not only is its performance more stable, the deque class is also easier to use because you don’t have to worry about adding or removing items from “the wrong end.”
In summary, I believe that collections.deque is an excellent choice for implementing a stack (LIFO queue) in Python.
Key Takeaways
- Python ships with several stack implementations that have slightly different performance and usage characteristics.
- collections.deque provides a safe and fast general-purpose stack implementation.
- The built-in list type can be used as a stack, but be careful to only append and remove items with append() and pop() in order to avoid slow performance.