双向队列
双向队列是一种前后两端都可以添加数据(入队)和删除数据(出队)的有序线性表
相对于单向队列的只能队尾入队队首出队,提供了更高灵活性
双向队列常用操作
| 方法名 | 描述 | 时间复杂度 |
|---|---|---|
| push_first() | 队首入队 | O(1) |
| push_last() | 队尾入队 | O(1) |
| pop_first() | 队首出队 | O(1) |
| pop_last() | 队尾出队 | O(1) |
| peek_first() | 获取队首元素 | O(1) |
| peek_last() | 获取队尾元素 | O(1) |
不同的编程语言实现的具体方法名可能有所区别
体验双向队列,可以直接使用编程语言已实现的双向队列类
# 导入python已实现的双向队列类
from collections import deque
# 实例化
deq = deque()
# 队尾入队
deq.append(1)
deq.append(2)
# 队首入队
deq.appendleft(3)
deq.appendleft(4)
print(deq)
# 队首元素
print(deq[0])
# 队尾元素
print(deq[-1])
# 队尾出队
deq.pop()
# 队首出队
deq.popleft()
print(deq)
deque([4, 3, 1, 2])
4
2
deque([3, 1])
双向队列可以看作是一个特殊的队列,兼具栈于单向队列特点
- 栈是一种后进先出(LIFO)的数据结构,只允许在一端进行插入和删除操作
- 队列是一种先进先出(FIFO)的数据结构,只允许在一端插入,在另一端删除
而双向队列则可以同时实现这两种功能,既可用于队列,也可用于栈,所以称为“双向”队列
双向队列的实现
选择用链表或数组作为底层数据结构
基于双向链表实现双向队列
通常使用双向链表实现双向队列,这也双端插入和删除的情况时间复杂度都为O(1),但如果要删除中间节点则需要O(n)复杂度,但是也很少需要删除中间节点,所以在大部分情况下效率还是很高的
将双向链表的头结点和尾节点分别视作双向队列的队首和队尾,实现两端添加和删除的操作
class ListNode:
"""双向链表"""
def __init__(self, val):
self.val: int = val
self.next: ListNode | None = None # 后继节点
self.prev: ListNode | None = None # 前驱节点
class LinkedListDeque:
"""基于双向链表实现双向队列"""
def __init__(self):
self._front = None
self._rear = None
self._size = 0
def size(self):
"""队列长度"""
return self._size
def is_empty(self):
return self._size == 0
def push(self, num, is_front=False):
# 构造节点
node = ListNode(num)
# 队空,首尾一致
if self.is_empty():
self._front = self._rear = node
elif is_front: # 队首入队
self._front.prev = node
node.next = self._front
self._front = node
else: # 队尾入队
self._rear.next = node
node.prev = self._rear
self._rear = node
self._size += 1
def push_first(self, num):
"""队首入队"""
self.push(num, True)
def push_last(self, num):
"""队尾入队"""
self.push(num)
def pop(self, is_front=False):
if self.is_empty():
raise IndexError('出队异常,队列为空')
if is_front: # 队首
val = self._front.val
fnext = self._front.next
# 头结点后移一个
if not fnext:
fnext.prev = None
self._front.next = None
self._front = fnext
else:
val = self._rear.val
rprev = self._rear.prev
# 尾结点前移一个
if not rprev:
rprev.next = None
self._rear.prev = None
self._rear = rprev
# 出队长度-1
self._size -= 1
return val
def pop_first(self):
"""队首出队"""
return self.pop(True)
def pop_last(self):
"""队尾出队"""
return self.pop()
def peek_first(self):
"""访问队首"""
if self.is_empty():
raise IndexError('访问队首异常,队空')
return self._front.val
def peek_last(self):
"""访问队尾"""
if self.is_empty():
raise IndexError('访问队首异常,队空')
return self._rear.val
def to_array(self):
"""返回数组用于打印"""
node = self._front
res = [0] * self.size()
for i in range(self.size()):
res[i] = node.val
node = node.next
return res
# test
deq = LinkedListDeque()
# 队尾入队
deq.push_last(1)
deq.push_last(2)
# 队首入队
deq.push_first(3)
deq.push_first(4)
print(deq.to_array())
# 队首元素
print(deq.peek_first())
# 队尾元素
print(deq.peek_last())
# 队尾出队
deq.pop_last()
# 队首出队
deq.pop_first()
print(deq.to_array())
[4, 3, 1, 2]
4
2
[3, 1]
数组实现双向队列
使用环形数组来实现双向队列,避免假溢出现象出现
class ArrayDeque:
"""基于环形数组实现的双向队列"""
def __init__(self, capacity):
# 创建时初始化数组容量
self._nums = [0] * capacity
# 头指针
self._front = 0
# 总长度
self._size = 0
def capacity(self):
"""当前容量大小"""
return len(self._nums)
def size(self):
"""获取数组长度"""
return self._size
def is_empty(self):
return self._size == 0
def index(self, i):
"""计算环形数组索引"""
# 取余操作实现数组首位相连
return (i + self.capacity()) % self.capacity()
def push_first(self, num):
"""队首入队"""
if self._size == self.capacity():
print('队满')
return
# 正常入队
self._front =self.index(self._front -1) # 首指针前移一位
self._nums[self._front] = num # 首指针地址赋值
self._size += 1 # 长度+1
def push_last(self, num):
"""队尾入队"""
if self._size == self.capacity():
print('队满')
return
# 正常入队
rear = self.index(self._front + self._size) # 计算尾指针
self._nums[rear] = num # 尾指针处赋值
self._size += 1
def pop_first(self):
"""队首出队"""
num = self.peek_first() # 保存首元素
self._front = self.index(self._front + 1) # 首指针后移一位
self._size -= 1 # 长度-1
return num
def pop_last(self):
"""队尾出队"""
num = self.peek_last()
self._size -= 1
def peek_first(self):
"""访问队首元素"""
if self.is_empty():
print('队空')
return
return self._nums[self._front]
def peek_last(self):
"""访问队尾元素"""
if self.is_empty():
print('队空')
return
rear = self.index(self._front + self._size - 1) # 尾指针位置
return self._nums[rear]
def to_array(self):
"""返回数组用于打印"""
res = []
for i in range(self._size):
res.append(self._nums[self.index(self._front + i)])
return res
# test
deq = ArrayDeque(10)
# 队尾入队
deq.push_last(1)
deq.push_last(2)
# 队首入队
deq.push_first(3)
deq.push_first(4)
print(deq.to_array())
# 队首元素
print(deq.peek_first())
# 队尾元素
print(deq.peek_last())
# 队尾出队
deq.pop_last()
# 队首出队
deq.pop_first()
print(deq.to_array())
[4, 3, 1, 2]
4
2
[3, 1]
优缺点
优点:
- 支持在队列两端插入和删除元素,能够快速地进行队列和栈的操作
- 可以模拟更复杂的数据结构,例如双端队列可以用来实现双端搜索、某些情况下的最短路径算法等
- 在某些操作场景下比普通队列和栈更高效
缺点:
- 双向队列需要使用更多内存空间来维护引用(指针),比普通队列和栈更占内存
- 插入和删除元素操作可能会引起引用(指针)的移动,导致时间复杂度比普通队列和栈更高
- 双向队列难以维护某些特定的性质,例如优先队列的排序特性,需要额外的实现方式