【数据结构与算法学习笔记】队列
前言
本文为个人学习的算法学习笔记,学习笔记,学习笔记,不是经验分享与教学,不是经验分享与教学,不是经验分享与教学,若有错误各位大佬轻喷(T^T)。主要使用编程语言为Python3,各类资料题目源于网络,主要自学途径为博学谷,侵权即删。
一、队列的基本定义与核心特性
1. 定义
队列是一种线性数据结构,严格遵循 “元素从队列尾(Tail)入队、从队列头(Head)出队” 的操作规则,整体满足 “先进先出(First In First Out,FIFO)” 逻辑,类似现实中的 “排队场景”—— 先排队的人先完成操作,后排队的人依次等待。、
2. 核心特性
- 操作限制:仅允许在队尾执行 “入队(添加元素)”,仅允许在队头执行 “出队(删除元素)”,不支持在队列中间插入 / 删除 / 访问元素。
- FIFO 原则:先入队的元素必然先出队。例如:元素按「3→2→5→7」的顺序入队后,队列结构为
Head → [3, 2, 5, 7] → Tail
,出队顺序只能是「3→2→5→7」。
二、队列的 Python 实现(两种核心方式)
根据课件内容,队列可通过数组(列表) 或链表实现,两种方式各有适配场景,以下为完整 Python 代码示例及解析。
1. 数组(列表)实现(含循环队列优化)
课件提到,普通数组实现队列会存在 “假溢出” 问题(队尾到达数组末尾时,即使队头有空闲空间也无法入队),因此需通过循环队列优化,将数组视为 “环形结构”。
实现思路
- 用列表
self.items
存储元素,用self.head
(队头索引)、self.tail
(队尾索引)标记两端,用self.size
记录队列最大容量。 - 入队:判断队列是否未满,将元素存入
self.tail
位置,self.tail
按环形逻辑更新((self.tail + 1) % self.size
)。 - 出队:判断队列是否非空,取出
self.head
位置的元素,self.head
按环形逻辑更新。
Python 代码
class CircularQueue:def __init__(self, capacity):"""初始化循环队列,capacity为队列最大容量"""self.capacity = capacity # 队列最大容量(对应数组长度)self.items = [None] * capacity # 存储元素的列表(数组)self.head = 0 # 队头索引,初始为0self.tail = 0 # 队尾索引,初始为0(指向待入队位置)def is_empty(self):"""判断队列是否为空:head == tail且队头元素为None"""return self.head == self.tail and self.items[self.head] is Nonedef is_full(self):"""判断队列是否已满:head == tail且队头元素非None"""return self.head == self.tail and self.items[self.head] is not Nonedef enqueue(self, value):"""入队:在队尾添加元素,返回是否成功"""if self.is_full():print("队列已满,无法入队")return Falseself.items[self.tail] = value # 元素存入队尾self.tail = (self.tail + 1) % self.capacity # 队尾环形更新print(f"元素{value}入队成功")return Truedef dequeue(self):"""出队:从队头删除并返回元素,若为空返回None"""if self.is_empty():print("队列为空,无法出队")return Nonevalue = self.items[self.head] # 取出队头元素self.items[self.head] = None # 清空队头位置(避免混淆空/满)self.head = (self.head + 1) % self.capacity # 队头环形更新print(f"元素{value}出队成功")return valuedef get_size(self):"""获取队列当前元素个数"""if self.is_empty():return 0elif self.is_full():return self.capacityelse:return (self.tail - self.head) % self.capacity# 示例:使用循环队列
if __name__ == "__main__":q = CircularQueue(5) # 初始化容量为5的循环队列q.enqueue(3) # 元素3入队成功q.enqueue(2) # 元素2入队成功q.enqueue(5) # 元素5入队成功q.enqueue(7) # 元素7入队成功print("当前队列长度:", q.get_size()) # 输出:4q.dequeue() # 元素3出队成功q.enqueue(9) # 元素9入队成功(利用环形空闲空间)print("当前队列长度:", q.get_size()) # 输出:4
2. 链表实现
课件指出,链表实现队列可天然避免 “假溢出”,支持动态扩容(无需预先指定容量),核心是用 “头节点” 标记队头、“尾节点” 标记队尾,减少元素移动操作。
实现思路
- 定义
Node
类表示链表节点(含value
(元素值)和next
(下一个节点指针))。 - 队列类
LinkedListQueue
用self.head
(头节点)、self.tail
(尾节点)、self.length
(元素个数)记录状态。 - 入队:创建新节点,若队列为空则头、尾节点均指向新节点;否则尾节点的
next
指向新节点,更新尾节点为新节点。 - 出队:若队列非空,记录头节点的元素值,更新头节点为
head.next
,若出队后为空则尾节点也置为 None。
Python 代码
class Node:"""链表节点类:存储元素值和下一个节点指针"""def __init__(self, value):self.value = value # 节点存储的元素值self.next = None # 指向后一个节点的指针class LinkedListQueue:def __init__(self):"""初始化空队列:头、尾节点均为None,长度为0"""self.head = None # 队头节点(指向第一个元素)self.tail = None # 队尾节点(指向最后一个元素)self.length = 0 # 队列当前元素个数def is_empty(self):"""判断队列是否为空:长度为0"""return self.length == 0def enqueue(self, value):"""入队:在队尾添加新节点"""new_node = Node(value) # 创建新节点if self.is_empty():# 空队列时,头、尾节点均指向新节点self.head = new_nodeself.tail = new_nodeelse:# 非空队列时,尾节点的next指向新节点,更新尾节点self.tail.next = new_nodeself.tail = new_nodeself.length += 1print(f"元素{value}入队成功")def dequeue(self):"""出队:从队头删除节点并返回元素值,空队列返回None"""if self.is_empty():print("队列为空,无法出队")return None# 记录队头节点的元素值(待返回)dequeued_value = self.head.value# 更新头节点为下一个节点self.head = self.head.nextself.length -= 1# 若出队后为空,尾节点也置为None(避免尾节点指向无效节点)if self.is_empty():self.tail = Noneprint(f"元素{dequeued_value}出队成功")return dequeued_valuedef get_size(self):"""获取队列当前元素个数"""return self.length# 示例:使用链表队列
if __name__ == "__main__":q = LinkedListQueue()q.enqueue(3) # 元素3入队成功q.enqueue(2) # 元素2入队成功q.enqueue(5) # 元素5入队成功print("当前队列长度:", q.get_size()) # 输出:3q.dequeue() # 元素3出队成功q.enqueue(7) # 元素7入队成功print("当前队列长度:", q.get_size()) # 输出:3
3. Python 内置模块collections.deque
(推荐)
课件提到队列的入队、出队时间复杂度均为 O (1),而 Python 内置的deque
(双端队列)恰好满足这一特性 —— 其append()
(队尾入队)和popleft()
(队头出队)操作均为 O (1),比普通列表的pop(0)
(O (n))更高效,适合实际开发。
示例代码
from collections import deque# 初始化队列(空队列)
q = deque()# 入队(队尾添加元素)
q.append(3)
q.append(2)
q.append(5)
q.append(7)
print("入队后队列:", q) # 输出:deque([3, 2, 5, 7])# 出队(队头删除元素)
dequeued_value = q.popleft()
print("出队元素:", dequeued_value) # 输出:3
print("出队后队列:", q) # 输出:deque([2, 5, 7])# 获取队列长度
print("当前队列长度:", len(q)) # 输出:3# 判断队列是否为空
print("队列是否为空:", len(q) == 0) # 输出:False
三、典型例题:LeetCode 933. 最近的请求次数(Python 实现)
课件明确将该题作为队列的典型应用场景 —— 处理 “按时间顺序产生的请求,筛选特定时间窗口内的请求”,核心思路是利用队列的 FIFO 特性清理 “过期请求”。
1. 题目描述
实现RecentCounter
类,计算最近 3000 毫秒内的请求次数:
- 方法
ping(int t)
:记录请求时间t
(毫秒,非递减),返回时间区间[t-3000, t]
内的请求总数。
2. 解题思路(对应课件逻辑)
- 用队列存储所有请求时间,每次
ping
时先 “清理队头”—— 删除所有小于t-3000
的过期时间(因t
非递减,队头是最早请求,过期则后续元素均过期); - 将当前请求时间
t
入队; - 队列长度即为 “最近 3000 毫秒内的请求数”,直接返回。、
3. Python 代码实现
from collections import dequeclass RecentCounter:def __init__(self):"""初始化:用deque存储请求时间"""self.requests = deque()def ping(self, t: int) -> int:"""处理请求t,返回最近3000毫秒内的请求数"""# 1. 清理过期请求:删除所有小于t-3000的时间(队头开始)while self.requests and self.requests[0] < t - 3000:self.requests.popleft()# 2. 当前请求时间t入队self.requests.append(t)# 3. 队列长度即为结果return len(self.requests)# 示例:验证课件中的测试场景
if __name__ == "__main__":rc = RecentCounter()print(rc.ping(1)) # 时间窗口[-2999,1],返回1print(rc.ping(100)) # 时间窗口[-2900,100],返回2print(rc.ping(3001)) # 时间窗口[1,3001],返回3(清理后队列:[1,100,3001])print(rc.ping(3002)) # 时间窗口[2,3002],返回3(清理后队列:[100,3001,3002])print(rc.ping(3110)) # 时间窗口[110,3110],返回3(清理后队列:[3001,3002,3110])
四、队列知识点小结(基于课件)
- 核心逻辑:队尾入队、队头出队,严格 FIFO;
- 实现方式:
- 数组实现:需用循环队列解决 “假溢出”,适合已知容量场景;
- 链表实现:动态扩容,无溢出问题,适合容量不确定场景;
- 推荐用
collections.deque
:兼顾效率与便捷性,O (1) 入队 / 出队。
- 时间复杂度:入队、出队均为 O (1),与数据规模无关;
- 典型应用:请求时间窗口统计(如 LeetCode 933)、任务调度、广度优先搜索(BFS)等。