双端队列
什么是双端队列?
双端队列(Double-ended Queue,简称Deque)是一种具有队列和栈特性的数据结构,允许在队列的两端进行插入和删除操作。双端队列可以在队首和队尾同时进行入队和出队操作,因此可以用来实现更灵活的数据操作。
双端队列的链表实现(顺序表实现过于简单仅附源码)
在本篇博客中,我们将深入探讨双端队列的链表实现。双端队列是一种特殊的数据结构,允许在队首和队尾进行插入和删除操作。我将逐步介绍每个部分的功能和实现方法,并在代码中演示这些概念。
1.实现的所有的方法
方法名 | 用途 |
---|---|
is_empty() | 检查双端队列是否为空 |
length() | 获取双端队列的长度 |
items() | 返回包含队列中所有元素的列表 |
push(data) | 在队尾添加元素 |
push_left(data) | 在队首添加元素 |
pop() | 从队首弹出元素 |
pop_right() | 从队尾弹出元素 |
pop_right2() | 从队尾弹出元素的另一种实现 |
peek() | 查看队首元素 |
2.实现前的准备
在开始之前,我们需要首先定义一个节点类,用于构建链表的节点。每个节点将包含数据以及指向下一个节点的引用。
class Node():
def __init__(self, data, next=None):
self.data = data
self.next = next
3.Deque类的基本结构
我们将使用Deque类来实现双端队列。这个类将包含队首和队尾节点的引用,以及一个用于记录队列长度的私有属性。
class Deque:
def __init__(self):
self.head = None
self.rear = None
self._length = 0
4.队列是否为空
我们首先需要一个方法来检查双端队列是否为空。
def is_empty(self):
return self._length == 0
5.获取队列长度
双端队列的长度是指队列中当前存储的元素数量。
def length(self):
return self._length
6.获取队列中的元素
我们可以编写一个方法来返回队列中所有元素的列表。
代码实现思路:
- 首先,我们创建一个空列表
res
,用于存储队列中的元素。 - 接着,我们将当前节点指针
cur
设置为队首节点,这样我们可以从队首开始遍历链表。 - 我们使用一个循环来遍历队列中的所有节点。在每次循环中,我们将当前节点的数据添加到列表
res
中。 - 然后,我们将当前节点指针
cur
移动到下一个节点,以便继续遍历。 - 循环继续,直到所有节点都被遍历完毕。
- 最后,我们返回存储了队列元素的列表
res
,这个列表包含了队列中的所有数据。
def items(self):
res = []
cur = self.head
while cur:
res.append(cur.data)
cur = cur.next
return res
7.向队尾添加元素
要向双端队列的队尾添加元素,我们将新节点链接到当前队尾节点,并更新队尾引用。
代码具体实现思路:
- 首先,我们创建一个新的节点
node
,将传入的数据data
存储在节点中。 - 接着,我们检查双端队列是否为空。如果为空,说明这是队列中的第一个节点,我们将
node
设置为队首节点和队尾节点。 - 如果队列不为空,我们将当前队尾节点的
next
引用链接到新节点node
,然后将node
设置为新的队尾节点。 - 最后,我们增加队列的长度计数
_length
。
代码如下:
def push(self, data):
node = Node(data)
if self.is_empty():
self.head = node
self.rear = node
else:
self.rear.next = node
self.rear = node
self._length += 1
8.向队首添加元素
向队首添加元素需要更新新节点的next引用以链接到当前头节点,并更新队首引用。
代码具体实现思路:
- 首先,我们创建一个新的节点
node
,将传入的数据data
存储在节点中。 - 然后,我们检查双端队列是否为空,如果为空,说明这是队列中的第一个节点,我们将
node
设置为队首节点和队尾节点。 - 如果队列不为空,我们将
node
的next
引用链接到当前队首节点,然后将node
设置为新的队首节点。 - 最后,我们增加队列的长度计数
_length
。
代码如下:
def push_left(self, data):
node = Node(data)
if self.is_empty():
self.head = node
self.rear = node
else:
node.next = self.head
self.head = node
self._length += 1
9.从队首弹出元素
从队首弹出元素意味着移除并返回队列中的第一个元素。
代码具体实现思路:
- 首先,我们检查双端队列是否为空。如果为空,抛出一个值错误,表示双端队列为空,不能执行弹出操作。
- 如果队列不为空,我们将队首节点
self.head
保存在变量res
中,这将是我们要弹出的节点。 - 然后,我们更新队首节点为当前队首节点的下一个节点,以将它从队列中移除。
- 我们同时减少队列的长度计数
_length
。 - 接下来,我们检查队首节点是否为空。如果队首节点为空,说明队列已经为空,我们还需要将队尾节点
self.rear
设置为 None,以保持队列的一致性。 - 最后,我们返回变量
res
中保存的节点的数据,即完成了从队首弹出元素的操作。
代码实现如下:
def pop(self):
if self.is_empty():
raise ValueError('双端队列为空')
else:
res = self.head
self.head = self.head.next
self._length -= 1
if self.head is None:
self.rear = None
return res.data
10.从队尾弹出元素
从队尾弹出元素需要处理不同情况,包括只有一个节点和多个节点。
代码具体实现思路:
- 首先,我们检查双端队列是否为空。如果为空,抛出一个值错误,表示双端队列为空,不能执行弹出操作。
- 接着,我们检查队列是否只有一个节点。如果是,说明队列中只有一个节点,我们将队首节点
self.head
设置为 None,同时将队尾节点self.rear
也设置为 None。 - 如果队列不为空且不止一个节点,我们先保存当前的队首节点
self.head
到变量res
中,这将是我们要弹出的节点。 - 然后,我们将队首节点
self.head
和队尾节点self.rear
都设置为 None,表示队列已经为空。 - 同时,我们减少队列的长度计数
_length
。 - 如果队列中有多个节点,我们需要遍历整个队列找到倒数第二个节点,即
cur
变量指向倒数第二个节点,prev
变量指向它的前一个节点。 - 我们遍历链表,将
prev
移动到cur
的前一个节点,将cur
移动到队列尾部节点,直到cur
指向队尾节点。 - 在这个过程中,我们断开
prev
和cur
之间的链接,从而将队尾节点从链表中分离。 - 最后,我们将队尾节点
self.rear
更新为变量prev
,将队列的长度计数_length
减少,并返回保存在变量res
中的节点的数据,即完成了从队尾弹出元素的操作。
def pop_right(self):
if self.is_empty():
raise ValueError('双端队列为空')
elif self.head == self.rear:
res = self.head
self.head, self.rear = None, None
self._length -= 1
return res.data
else:
cur = self.head
prev = None
while cur.next:
prev = cur
cur = cur.next
prev.next = None
self.rear = prev
self._length -= 1
return cur.data
11.从队尾弹出元素的另一种实现
我们还可以通过另一种方式实现从队尾弹出元素的操作,相较于上一种方法代码更为简洁。
代码实现思路:
- 首先,我们检查双端队列是否为空。如果为空,抛出一个值错误,表示双端队列为空,不能执行弹出操作。
- 接着,我们检查队列是否只有一个节点。如果是,说明队列中只有一个节点,我们将队首节点
self.head
和队尾节点self.rear
都设置为 None。 - 如果队列不为空且不止一个节点,我们先保存当前的队尾节点
self.rear
到变量res
中,这将是我们要弹出的节点。 - 然后,我们遍历整个队列,将变量
cur
移动到倒数第二个节点的位置,将变量prev
指向cur
的前一个节点。 - 当遍历完成后,
cur
将指向队尾节点的前一个节点,而prev
指向cur
的前一个节点。 - 我们将队尾节点
self.rear
更新为变量cur
,断开cur
和res
之间的链接,从而将队尾节点从链表中分离。 - 同时,我们减少队列的长度计数
_length
。 - 最后,我们返回保存在变量
res
中的节点的数据,即完成了从队尾弹出元素的操作。
代码如下:
def pop_right2(self):
if self.is_empty():
raise ValueError('双端队列为空')
else:
if self.head == self.rear:
res = self.head
self.head, self.rear = None, None
else:
cur = self.head
while cur.next != self.rear:
cur = cur.next
res = self.rear
self.rear = cur
cur.next = None
self._length -= 1
return res.data
12.查看队首元素
要查看队列中的队首元素,我们可以编写一个简单的方法。
def peek(self):
if self.is_empty():
raise ValueError('双端队列为空')
else:
return self.head.data
双端队列的完整代码
链表实现
'''
@Time : 2023-08-15 15:01
@Author : TAGRENLA
@File : 双端队列 链表实现.py
'''
class Node():
def __init__(self,data,next = None):
self.data = data
self.next = next
class Deque:
def __init__(self):
self.head = None
self.rear = None
self._length = 0
def is_empty(self):
return self._length == 0
def length(self):
return self._length
def items(self):
res = []
cur = self.head
while cur:
res.append(cur.data)
cur = cur.next
return res
def push(self,data):
node = Node(data)
if self.is_empty():
self.head = node
self.rear = node
else:
self.rear.next = node
self.rear = node
self._length += 1
def push_left(self,data):
node = Node(data)
if self.is_empty():
self.head = node
self.rear = node
else:
node.next = self.head
self.head = node
self._length += 1
def pop(self):
# 弹出队首元素
if self.is_empty():
raise ValueError('双端队列为空')
else:
res = self.head
self.head = self.head.next
self._length -= 1
# 处理只有尾节点的情况
if self.head is None:
self.rear = None
return res.data
def pop_right(self):
# 弹出队尾元素
if self.is_empty():
raise ValueError('双端队列为空')
elif self.head == self.rear:
res = self.head
self.head,self.rear = None,None
self._length -= 1
return res.data
else:
cur = self.head
prev = None
while cur.next:
prev = cur
cur = cur.next
prev.next = None
self.rear = prev
self._length -= 1
return cur.data
def pop_right2(self):
# 弹出队尾元素
if self.is_empty():
raise ValueError('双端队列为空')
else:
if self.head == self.rear:
# 只有一个节点
res = self.head
self.head = None
self.rear = None
else:
# 多个节点
cur = self.head
while cur.next != self.rear:
cur = cur.next
res = self.rear
self.rear = cur
cur.next = None
self._length -= 1
return res.data
def peek(self):
# 返回队首元素
if self.is_empty():
raise ValueError('双端队列为空')
else:
return self.head.data
if __name__ == '__main__':
deque = Deque()
deque.push(1)
deque.push(2)
deque.push(3)
deque.push(4)
deque.push(5)
deque.push(6)
print(deque.pop())
print(deque.pop_right())
print(deque.pop())
print(deque.pop_right())
print(deque.items())
# print(deque.items)
# print(deque.peek())
双端队列顺序表实现
'''
@Time : 2023-08-15 14:24
@Author : TAGRENLA
@File : 双端队列_顺序表.py
'''
# 顺序表实现
class Deque():
def __init__(self): # 初始化队列 顺序表实现
self.items = []
def is_empty(self): # 返回队列是否为空
return self.items == 0
def length(self): # 返回队列长度
return len(self.items)
def push(self,item): # 在队尾添加元素
self.items.append(item)
def push_left(self,item): # 在队首添加元素
self.items.insert(0,item)
def pop(self): # 弹出队首元素
return self.items.pop(0)
def pop_right(self): #弹出队尾元素
return self.items.pop()
def peek(self): # 输出队首元素
return self.items[0]
if __name__ == '__main__':
deque = Deque()
deque.push(1)
deque.push(2)
deque.push_left(3)
deque.push_left(4)
deque.push_left(5)
deque.push_left(6)
print(deque.items)
print(deque.peek())
双端循环队列
什么是双端循环队列?
双端循环队列(Circular Deque),也被称为循环双端队列,是一种特殊类型的双端队列,具有循环性质。它的特点是队列的首尾是相连的,形成一个环,使得在队列满时可以重新利用之前的空间。双端循环队列常常用于需要高效利用存储空间的情况,比如循环缓冲区、操作系统任务调度等。
双端循环队列的特点包括:
- 循环性质: 在队列的逻辑结构中,队列的两端是相连的,使得队列成为一个环形的结构。这使得队列可以循环利用内存空间。
- 支持双向操作: 可以在队列的头部和尾部同时进行插入和删除操作,使其在处理不同问题时具有更灵活的用途。
- 循环索引: 为了实现循环性质,队列的实现通常使用一个循环索引,使队列在尾部元素之后绕回到头部元素。
- 大小限制: 和普通队列一样,循环双端队列可以有一个固定的容量上限。
双端循环队列在许多问题中具有实用性,例如在实现缓存、循环任务调度、模拟系统等方面都可以得到应用。通过在两端同时支持插入和删除操作,并且可以循环利用内存,它提供了更高效的数据管理方式。
collections模块实现
python中的collections模块中deque类就含有其功能,具体功能见下表:
方法名 | 用途 |
---|---|
append(x) | 在队尾添加元素 x |
appendleft(x) | 在队首添加元素 x |
pop() | 弹出并返回队尾的元素 |
popleft() | 弹出并返回队首的元素 |
extend(iterable) | 在队尾扩展添加一个可迭代对象的元素 |
extendleft(iterable) | 在队首扩展添加一个可迭代对象的元素 |
rotate(n=1) | 旋转队列的元素,正数向右旋转,负数向左旋转 |
count(x) | 计算队列中元素 x 的数量 |
remove(value) | 从队列中删除首次出现的值 value |
reverse() | 反转队列中的元素 |
clear() | 清空队列中的所有元素 |
maxlen | 双端队列的最大长度 |
示例代码:
from collections import deque
# 创建一个双端队列
d = deque()
# 在队首和队尾插入元素
d.appendleft(1) # 插入到队首
d.append(2) # 插入到队尾
d.append(3)
d.appendleft(0)
d.append(4)
# 输出队列的元素以及队首和队尾元素
elements = list(d) # 将deque转换为列表
print("队列元素:", elements)
print("队首元素:", d[0]) # 双端队列支持索引访问
print("队尾元素:", d[-1])
# 删除队首和队尾元素
d.popleft() # 删除队首元素
d.pop() # 删除队尾元素
# 输出删除后的队列元素以及队首和队尾元素
elements_after_deletion = list(d)
print("删除后的队列元素:", elements_after_deletion)
print("删除后的队首元素:", d[0])
print("删除后的队尾元素:", d[-1])
顺序表实现双端循环队列
class CircularDeque:
def __init__(self, capacity):
self.capacity = capacity
self.queue = [None] * capacity
self.front = 0
self.rear = 0
self.size = 0
def is_empty(self):
return self.size == 0
def is_full(self):
return self.size == self.capacity
def insert_front(self, value):
if self.is_full():
return False
self.front = (self.front - 1 + self.capacity) % self.capacity
self.queue[self.front] = value
self.size += 1
return True
def insert_last(self, value):
if self.is_full():
return False
self.queue[self.rear] = value
self.rear = (self.rear + 1) % self.capacity
self.size += 1
return True
def delete_front(self):
if self.is_empty():
return False
self.front = (self.front + 1) % self.capacity
self.size -= 1
return True
def delete_last(self):
if self.is_empty():
return False
self.rear = (self.rear - 1 + self.capacity) % self.capacity
self.size -= 1
return True
def get_front(self):
if self.is_empty():
return None
return self.queue[self.front]
def get_rear(self):
if self.is_empty():
return None
return self.queue[(self.rear - 1 + self.capacity) % self.capacity]
# 创建一个容量为5的双端循环队列
deque = CircularDeque(5)
# 在队首和队尾插入元素
deque.insert_front(1)
deque.insert_last(2)
deque.insert_last(3)
deque.insert_front(0)
deque.insert_last(4)
# 打印队列的元素和队首、队尾
print("队列元素:", [deque.queue[i] for i in range(deque.capacity)])
print("队首元素:", deque.get_front())
print("队尾元素:", deque.get_rear())
# 删除队首和队尾元素
deque.delete_front()
deque.delete_last()
# 打印删除后的队列元素和队首、队尾
print("删除后的队列元素:", [deque.queue[i] for i in range(deque.capacity)])
print("删除后的队首元素:", deque.get_front())
print("删除后的队尾元素:", deque.get_rear())
链表实现双端循环队列
class Node:
def __init__(self, value):
self.value = value
self.next = None
class CircularDeque:
def __init__(self, capacity):
self.capacity = capacity
self.size = 0
self.front = None
self.rear = None
def is_empty(self):
return self.size == 0
def is_full(self):
return self.size == self.capacity
def insert_front(self, value):
if self.is_full():
return False
new_node = Node(value)
if self.is_empty():
self.front = new_node
self.rear = new_node
else:
new_node.next = self.front
self.front = new_node
self.rear.next = self.front # 完成循环连接
self.size += 1
return True
def insert_rear(self, value):
if self.is_full():
return False
new_node = Node(value)
if self.is_empty():
self.front = new_node
self.rear = new_node
else:
self.rear.next = new_node
new_node.next = self.front # 完成循环连接
self.rear = new_node
self.size += 1
return True
def delete_front(self):
if self.is_empty():
return False
if self.front == self.rear:
self.front = None
self.rear = None
else:
self.front = self.front.next
self.rear.next = self.front # 更新循环连接
self.size -= 1
return True
def delete_rear(self):
if self.is_empty():
return False
if self.front == self.rear:
self.front = None
self.rear = None
else:
cur = self.front
while cur.next != self.rear:
cur = cur.next
cur.next = self.front # 更新循环连接
self.rear = cur
self.size -= 1
return True
def get_front(self):
if self.is_empty():
return None
return self.front.value
def get_rear(self):
if self.is_empty():
return None
return self.rear.value
# 创建容量为5的循环双端队列
deque = CircularDeque(5)
# 在队首和队尾插入元素
deque.insert_front(1)
deque.insert_rear(2)
deque.insert_rear(3)
deque.insert_front(0)
deque.insert_rear(4)
# 输出队列的元素以及队首和队尾元素
elements = []
current = deque.front
for _ in range(deque.size):
elements.append(current.value)
current = current.next
print("队列元素:", elements)
print("队首元素:", deque.get_front())
print("队尾元素:", deque.get_rear())
# 删除队首和队尾元素
deque.delete_front()
deque.delete_rear()
# 输出删除后的队列元素以及队首和队尾元素
elements_after_deletion = []
current = deque.front
for _ in range(deque.size):
elements_after_deletion.append(current.value)
current = current.next
print("删除后的队列元素:", elements_after_deletion)
print("删除后的队首元素:", deque.get_front())
print("删除后的队尾元素:", deque.get_rear())