- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
栈和队列是在程序设计中常见的数据类型,从数据结构的角度来讲,栈和队列也是线性表,是操作受限的线性表,它们的基本操作是线性表操作的子集,但从数据类型的角度来讲,它们与线性表又有着巨大的不同。本节将介绍队列的定义及其不同实现,并且给出队列的一些实际应用。
通过本节学习,应掌握以下内容:
队列 (Queue
) 是插入和删除操作分别被限制在序列两端的线性表(新元素从一段插入其中,则只能从另一端删除已有元素),对于队列而言,允许插入元素的一端称为队尾 (rear
),而允许取出元素的一段则称为队头 (front
)。
在队列中,数据到达的顺序很重要。最新添加的元素位于必须在队尾,在队列中时间最长的元素则排在最前面,这种排序原则被称作先进先出 (first in first out
,FIFO
),或后进后出 (last in first out
, LILO
)。
队列在现实中的例子随处可见,我们生活中就餐所需要进行的排队就是一个队列的现实例子,最早进入队列的人可以首先就餐,而后来者需要排于队尾等待。假设队列为 Q = ( a 0 , a 1 , … , a n ) Q=(a_0, a_1, …, a_n)Q=(a0,a1,…,an),则队头元素为 a 0 a_0a0,a n a_nan 为队尾元素,队列中元素按 a 0 , a 1 , … , a n a_0, a_1, …, a_na0,a1,…,an 的顺序入队 (enqueue
),出队 (dequeue
) 也只能按照这个顺序依次退出,队头元素是第一个出队 (dequeue
) 的元素,而只有 a 0 , a 1 , … , a n − 1 a_0, a_1, …, a_{n-1}a0,a1,…,an−1 在都出队后,才 a n a_nan 能退出队列。下图是一个简单的队列示例:
通过观察元素的添加和移除顺序,就可以快速理解队列所蕴含的思想。下图展示了队列元素的入队和出队过程,队列中元素的插入顺序和移除顺序是完全相同的。
除了主要的操作(入队和出队)外,队列还具有初始化、判队空和求队长度等辅助操作。具体而言,队列的抽象数据类型定义如下:
ADT Queue:
数据对象:D = a i ∣ a i ∈ D a t a T y p e , i = 1 , 2 , . . . , n , n ≥ 0 D={a_i|a_i∈DataType, i=1,2,...,n,n\geq0}D=ai∣ai∈DataType,i=1,2,...,n,n≥0
数据关系:R = < a i , a i + 1 > ∣ a i , a i + 1 ∈ D , i = 1 , 2 , . . . , n − 1 R={<a_{i},a_{i+1}>|a_i,a_{i+1}∈D,i=1,2,...,n-1}R=<ai,ai+1>∣ai,ai+1∈D,i=1,2,...,n−1
a 1 a_1a1为队头元素,a n a_nan为队尾元素
基本操作:
1. itit(): 初始化队列
创建一个空队列
2. size(): 求取并返回队列中所含元素的个数 n
若队列为空,则返回整数0
3. isempty(): 判断是否为空队列
判断队列中是否存储元素
4. enqueue(data): 入队
将元素 data 插入队尾
5. dequeue(): 出队
删除并返回队尾元素
4. head(): 取队头元素
返回队头元素,但并不删除元素
队列具有广泛的应用场景,例如:
除了以上应用外,我们在之后的学习中还将看到队列用作许多算法的辅助数据结构。
和线性表一样,队列同样有顺序存储和链式存储两种存储表示方式。
类似于顺序栈,队列的顺序存储结构利用一组地址连续的存储单元依次存放从队列头到队列尾依次存放,同时需要用两个指针 front
和 rear
分别指示队列头元素和队列尾元素的位置。初始化空队列时,front=rear=0
,当元素入队时,rear 加 1
,而元素出队时,front 加 1
,如下所示:
从以上示例中,可以很清楚地看到位于队头之前的空间被浪费了,为了解决这一问题,我们可以假设顺序队列为环状空间,将最后一个元素和第一个元素视为连续的,如下图所示:
从上图可以看出,当队列满时与队列空时,都有front=rear
,因此无法通过 front==rear
来判断队列是否已满。针对这一问题,有两种解决方法:1) 设置标志来指示队列中是否还有空闲空间;2) 放弃一个元素空间,即当 front
指针在 rear
指针的下一位时 ((rear+1)%max_size=front
) 队列为满。以下实现使用第二种方案。
同样顺序队列可以是固定长度和动态长度,当队列满时,定长顺序队列会抛出栈满异常,动态顺序队列则会动态申请空闲空间。
顺序队列的初始化需要 4 部分信息:queue
列表用于存储数据元素,max_size
用于存储 queue
列表的最大长度,以及 front
和 rear
分别用于记录队头元素和队尾元素的索引:
class Queue:
def __init__(self, max_size=5):
self.max_size = max_size
self.queue = [None] * self.max_size
self.front = 0
self.rear = 0
由于 front
和 rear
分别用于记录队头元素和队尾元素的索引,因此我们可以方便的计算出队列的长度;同时我们需要考虑队列为循环队列,front
可能大于 rear
,不能直接通过 rear-front
,我们需要利用公式计算解决此问题:
( r e a r − f r o n t + m a x _ s i z e ) % m a x _ s i z e (rear-front+max_size) % max_size(rear−front+max_size)%max_size
Python
实现如下:
def size(self):
return (self.rear-self.front+self.max_size) % self.max_size
根据 front
和 rear
的值可以方便的判断队列是否为空:
def isempty(self):
return self.rear==self.front
根据 front
和 rear
的值可以方便的判断队列是否还有空余空间:
def isfull(self):
return ((self.rear+1) % self.max_size == self.front)
入队时,需要首先判断队列中是否还有空闲空间,然后根据队列为定长顺序队列或动态顺序队列,入队操作稍有不同:
[定长顺序队列的入队操作] 如果队满,则引发异常:
def enqueue(self, data):
if not self.isfull():
self.queue[self.rear] = data
self.rear = (self.rear+1) % self.max_size
else:
raise IndexError("Full Queue Exception")
[动态顺序队列的入队操作] 如果队列满,则首先申请新空间,然后再执行入队操作:
def resize(self):
new_size = 2 * self.max_size
new_queue = [None] * new_size
for i in range(self.max_size):
new_queue[i] = self.queue[i]
self.queue = new_queue
self.max_size = new_size
def enqueue(self, data):
if self.isfull():
self.resize()
self.queue[self.rear] = data
self.rear = (self.rear+1) % self.max_size
入队的时间复杂度为 O ( 1 ) O(1)O(1)。这里需要注意的是,虽然当动态顺序队列满时,原队列中的元素需要首先复制到新队列中,然后添加新元素,但参考《顺序表及其操作实现》中顺序表追加操作的介绍,由于 n
次入队操作的总时间 T ( n ) T(n)T(n) 与 O ( n ) O(n)O(n) 成正比,因此队栈的摊销时间复杂度可以认为是 O ( 1 ) O(1)O(1)。
若队列不空,则删除并返回队头元素:
def dequeue(self):
if not self.isempty():
result = self.queue[self.front]
self.front = (self.front+1) % self.max_size
return result
else:
raise IndexError("Empty Queue Exception")
若队列不空,则返回队头元素:
def head(self):
if not self.isempty():
result = self.queue[self.front]
return result
else:
raise IndexError("Empty Queue Exception")
队列的另一种存储表示方式是使用链式存储结构,因此也常称为链队列,其中 enqueue
操作是通过在链表尾部插入元素来实现的,dequeue
操作是通过从头部删除节点来实现的。
队列的结点实现与链表并无差别:
class Node:
def __init__(self, data=None):
self.data = data
self.next = None
def __str__(self):
return str(self.data)
队列的初始化函数中,使其队头指针 front
和 rear
均指向 None
,并初始化队列长度:
class Queue:
def __init__(self):
self.front = None
self.rear = None
self.num = 0
返回 size
的值用于求取队列的长度,如果没有 size
属性,则需要遍历整个链表才能得到队列长度:
def size(self):
return self.num
根据队列的长度可以很容易的判断队列是否为空队列:
def isempty(self):
return self.num <= 0
入队时,在队尾插入新元素,并且需要将队尾指针 rear
指向新元素,如果队列为空,需要将队头指针 front
也指向此元素:
def enqueue(self, data):
node = Node(data)
if self.front is None:
self.rear = node
self.front = self.rear
else:
self.rear.next = node
self.rear = node
self.num += 1
若队列不空,则删除并返回队头元素,并且需要更新队头指针 front
指向原队头结点的后继结点,若出队元素尾队中最后一个结点,则更新队尾指针 rear
:
def dequeue(self):
if self.isempty():
raise IndexError("Empty Queue Exception")
result = self.front.data
self.front = self.front.next
self.num -= 1
if self.isempty():
self.rear = self.front
return result
若队列不空,则返回队头元素:
def head(self):
if self.isempty():
raise IndexError("Empty Queue Exception")
result = self.front.data
return result
队列的不同实现对比与栈的不同实现类似,可以参考《栈及其操作实现》。
接下来,我们首先测试上述实现的队列,以验证操作的有效性,然后利用实现的基本操作来解决实际算法问题。
首先初始化一个顺序队列 queue
,然后测试相关操作:
# 初始化一个最大长度为5的队列
q = Queue(5)
print('队列空?', q.isempty())
for i in range(4):
print('入队元素:', i)
q.enqueue(i)
print('队列满?', q.isfull())
print('队头元素:', q.head())
print('队列长度为:', q.size())
while not q.isempty():
print('出队元素:', q.dequeue())
测试程序输出结果如下:
队列空? True
入队元素: 0
入队元素: 1
入队元素: 2
入队元素: 3
# 队列中弃用一个空间,因此队列中有4个元素即满
队列满? True
队头元素: 0
队列长度为: 4
出队元素: 0
出队元素: 1
出队元素: 2
出队元素: 3
首先初始化一个链队列 queue
,然后测试相关操作:
# 初始化新队列
q = Queue()
print('队列空?', q.isempty())
for i in range(4):
print('入队元素:', i)
q.enqueue(i)
print('队头元素:', q.head())
print('队列长度为:', q.size())
while not q.isempty():
print('出队元素:', q.dequeue())
测试程序输出结果如下:
队列空? True
入队元素: 0
入队元素: 1
入队元素: 2
入队元素: 3
队头元素: 0
队列长度为: 4
出队元素: 0
出队元素: 1
出队元素: 2
出队元素: 3
[1] 考虑经典的约瑟夫斯环问题,n
个人围成一圈,从第 1
个人开始报数,第 m
个将被淘汰,重复上述过程,直到只剩下一个人,其余人都将被淘汰。
我们使用队列来模拟一个环,如下图所示,从队列的头部开始,将位于队首的人移出队列并立刻将其插入队列的尾部,之后此人会一直等待,直到其再次到达队列的头部。在出列和入列 m-1
次之后,位于队列头部的人出局(即第 m
个人),然后新一轮游戏开始;如此反复,直到队列中只剩下一个人(队列的大小为 1
)。
def Josephus(name_list, m):
queue = Queue()
for name in name_list:
queue.enqueue(name)
while queue.size() > 1:
for i in range(m-1):
queue.enqueue(queue.dequeue())
queue.dequeue()
return queue.head()
# n=6, m=5
result = Josephus(["A", "B", "C", "D", "E", "F"], 5)
print('幸存的人为', result)
程序输出结果如下:
幸存的人为 A
线性表基本概念
顺序表及其操作实现
单链表及其操作实现
栈及其操作实现
我正在处理一组标记为 160 个组的 173k 点。我想通过合并最接近的(到 9 或 10 个组)来减少组/集群的数量。我搜索过 sklearn 或类似的库,但没有成功。 我猜它只是通过 knn 聚类
我有一个扁平数字列表,这些数字逻辑上以 3 为一组,其中每个三元组是 (number, __ignored, flag[0 or 1]),例如: [7,56,1, 8,0,0, 2,0,0, 6,1,
我正在使用 pipenv 来管理我的包。我想编写一个 python 脚本来调用另一个使用不同虚拟环境(VE)的 python 脚本。 如何运行使用 VE1 的 python 脚本 1 并调用另一个 p
假设我有一个文件 script.py 位于 path = "foo/bar/script.py"。我正在寻找一种在 Python 中通过函数 execute_script() 从我的主要 Python
这听起来像是谜语或笑话,但实际上我还没有找到这个问题的答案。 问题到底是什么? 我想运行 2 个脚本。在第一个脚本中,我调用另一个脚本,但我希望它们继续并行,而不是在两个单独的线程中。主要是我不希望第
我有一个带有 python 2.5.5 的软件。我想发送一个命令,该命令将在 python 2.7.5 中启动一个脚本,然后继续执行该脚本。 我试过用 #!python2.7.5 和http://re
我在 python 命令行(使用 python 2.7)中,并尝试运行 Python 脚本。我的操作系统是 Windows 7。我已将我的目录设置为包含我所有脚本的文件夹,使用: os.chdir("
剧透:部分解决(见最后)。 以下是使用 Python 嵌入的代码示例: #include int main(int argc, char** argv) { Py_SetPythonHome
假设我有以下列表,对应于及时的股票价格: prices = [1, 3, 7, 10, 9, 8, 5, 3, 6, 8, 12, 9, 6, 10, 13, 8, 4, 11] 我想确定以下总体上最
所以我试图在选择某个单选按钮时更改此框架的背景。 我的框架位于一个类中,并且单选按钮的功能位于该类之外。 (这样我就可以在所有其他框架上调用它们。) 问题是每当我选择单选按钮时都会出现以下错误: co
我正在尝试将字符串与 python 中的正则表达式进行比较,如下所示, #!/usr/bin/env python3 import re str1 = "Expecting property name
考虑以下原型(prototype) Boost.Python 模块,该模块从单独的 C++ 头文件中引入类“D”。 /* file: a/b.cpp */ BOOST_PYTHON_MODULE(c)
如何编写一个程序来“识别函数调用的行号?” python 检查模块提供了定位行号的选项,但是, def di(): return inspect.currentframe().f_back.f_l
我已经使用 macports 安装了 Python 2.7,并且由于我的 $PATH 变量,这就是我输入 $ python 时得到的变量。然而,virtualenv 默认使用 Python 2.6,除
我只想问如何加快 python 上的 re.search 速度。 我有一个很长的字符串行,长度为 176861(即带有一些符号的字母数字字符),我使用此函数测试了该行以进行研究: def getExe
list1= [u'%app%%General%%Council%', u'%people%', u'%people%%Regional%%Council%%Mandate%', u'%ppp%%Ge
这个问题在这里已经有了答案: Is it Pythonic to use list comprehensions for just side effects? (7 个答案) 关闭 4 个月前。 告
我想用 Python 将两个列表组合成一个列表,方法如下: a = [1,1,1,2,2,2,3,3,3,3] b= ["Sun", "is", "bright", "June","and" ,"Ju
我正在运行带有最新 Boost 发行版 (1.55.0) 的 Mac OS X 10.8.4 (Darwin 12.4.0)。我正在按照说明 here构建包含在我的发行版中的教程 Boost-Pyth
学习 Python,我正在尝试制作一个没有任何第 3 方库的网络抓取工具,这样过程对我来说并没有简化,而且我知道我在做什么。我浏览了一些在线资源,但所有这些都让我对某些事情感到困惑。 html 看起来
我是一名优秀的程序员,十分优秀!