栈和队列
栈和队列是两种基本的数据结构,这两种数据结构经常被放在一起讨论。它们一般基于链表来实现,可以作为链表的一个具体应用实例。
基本概念
栈
栈(Stack)是一种后进先出(LIFO,Last-In-First-Out)的数据结构,即最后进入的元素会被最先取出。栈有点像手枪的弹夹,数据放入栈,就好像子弹压入弹夹,先压入的子弹一定是后出来。
栈有三个基本操作:
- 压栈 push: 添加元素到栈顶
- 弹出 pop: 移除栈顶元素
- 查看 top: 查看栈顶元素而不移除它
队列
队列(Queue)是一种先进先出(FIFO,First-In-First-Out)的数据结构,即最先进入的元素会被最先取出。队列的行为就像是排队买票,数据进入队列,就好像顾客进入排队,先排进去的顾客一定会被先处理,然后先出队伍。
队列也有三个类似的基本操作:
- 入队 enqueue: 在队列末尾添加一个元素
- 出队 dequeue: 移除队列开头的元素
- 查看 front: 查看队列开头的元素而不移除它
双向队列
双向队列(deque,全称double-ended queue)是一种具有队列和栈的性质的数据结构。双向队列中的元素可以从两端进队和出队,因此,只要把入队或出队限制在某一段,就可以把它当做栈或队列来用。在实际应用中,都会使用双向链表来代替栈或队列。
双向队列是由一个双向链表实现的,但是其限制了插入和删除操作只能在链表头尾两端进行。因此,它的入队出队时间复杂度是与链表插入删除相同的,都是 。
在 Python 中,自 带有一个实现好的双向队列,它是在 collections 模块中的 deque 类。deque 的基本操作如下:
- 右侧添加 append(): 添加元素到右端。
- 左侧添加 appendleft(): 添加元素到左端。
- 右端弹出 pop(): 弹出右端元素。
- 左侧弹出 popleft(): 弹出左端元素。
使用链表实现双向队列
代码示例:
class Node:
def __init__(self, value):
self.value = value
self.prev = None
self.next = None
class Deque:
def __init__(self):
self.head = None
self.tail = None
def append(self, value):
new_node = Node(value)
if not self.tail: # 如果队列为空
self.head = self.tail = new_node
else:
self.tail.next = new_node
new_node.prev = self.tail
self.tail = new_node
def appendleft(self, value):
new_node = Node(value)
if not self.head: # 如果队列为空
self.head = self.tail = new_node
else:
self.head.prev = new_node
new_node.next = self.head
self.head = new_node
def pop(self):
if not self.tail:
raise IndexError("pop from an empty deque")
value = self.tail.value
if self.head == self.tail: # 只有一个元素
self.head = self.tail = None
else:
self.tail = self.tail.prev
self.tail.next = None
return value
def popleft(self):
if not self.head:
raise IndexError("pop from an empty deque")
value = self.head.value
if self.head == self.tail: # 只有一个元素
self.head = self.tail = None
else:
self.head = self.head.next
self.head.prev = None
return value
# 测试
deque = Deque()
deque.append(1)
deque.append(2)
deque.appendleft(0)
print(deque.pop()) # 输出: 2
print(deque.popleft()) # 输出: 0
在上面的程序中,每次 append 和 appendleft 都会创建一个新的 Node 实例。我们通过调整头部和尾部节点的引用来实现 appendleft 和 append。类似地,popleft 和 pop 操作会从链表的相应端点移除一个节点,并更新头部或尾部引用。
应用
栈和队列非常适合于那些需要对数据进行快速增减操作的场景,如:用作缓存;在线程间通信,尤其是在生产者消费者问题中,可以使用队列来作为生产者和消费者之间的工作队列。在对树或图进行遍历时,也经常需要使用栈或队列来保存数据。