Python数据结构之队列详解


Posted in Python onMarch 21, 2022

0. 学习目标

栈和队列是在程序设计中常见的数据类型,从数据结构的角度来讲,栈和队列也是线性表,是操作受限的线性表,它们的基本操作是线性表操作的子集,但从数据类型的角度来讲,它们与线性表又有着巨大的不同。本节将介绍队列的定义及其不同实现,并且给出队列的一些实际应用。
通过本节学习,应掌握以下内容:

  • 队列的基本概念及不同实现方法
  • 队列基本操作的实现及时间复杂度
  • 利用队列的基本操作实现复杂算法

1. 队列的基本概念

1.1 队列的基本概念

队列 (Queue) 是插入和删除操作分别被限制在序列两端的线性表(新元素从一段插入其中,则只能从另一端删除已有元素),对于队列而言,允许插入元素的一端称为队尾 (rear),而允许取出元素的一段则称为队头 (front)。

在队列中,数据到达的顺序很重要。最新添加的元素位于必须在队尾,在队列中时间最长的元素则排在最前面,这种排序原则被称作先进先出 (first in first out,FIFO),或后进后出 (last in first out, LILO)。

队列在现实中的例子随处可见,我们生活中就餐所需要进行的排队就是一个队列的现实例子,最早进入队列的人可以首先就餐,而后来者需要排于队尾等待。假设队列为 Q=(a0,a1,...,an),则队头元素为a0,an为队尾元素,队列中元素按a0,a1,...,an的顺序入队 (enqueue),出队 (dequeue) 也只能按照这个顺序依次退出,队头元素是第一个出队 (dequeue) 的元素,而只有a0,a1,...,an-1在都出队后,才an 能退出队列。下图是一个简单的队列示例:

Python数据结构之队列详解

通过观察元素的添加和移除顺序,就可以快速理解队列所蕴含的思想。下图展示了队列元素的入队和出队过程,队列中元素的插入顺序和移除顺序是完全相同的。

Python数据结构之队列详解

1.2 队列抽象数据类型

除了主要的操作(入队和出队)外,队列还具有初始化、判队空和求队长度等辅助操作。具体而言,队列的抽象数据类型定义如下:

Python数据结构之队列详解

1.3 队列的应用场景

队列具有广泛的应用场景,例如:

  • 在作业具有相同优先级的前提下,操作系统会按照作业的到达顺序安排作业;
  • 击键操作被放入一个类似于队列的缓冲区,以便对应的字符按正确的顺序显示;
  • 模拟现实世界的队列;
  • 多道程序设计;
  • 异步数据传输;

除了以上应用外,我们在之后的学习中还将看到队列用作许多算法的辅助数据结构。

2. 队列的实现

和线性表一样,队列同样有顺序存储和链式存储两种存储表示方式。

2.1 顺序队列的实现

类似于顺序栈,队列的顺序存储结构利用一组地址连续的存储单元依次存放从队列头到队列尾依次存放,同时需要用两个指针 front 和 rear 分别指示队列头元素和队列尾元素的位置。初始化空队列时,front=rear=0,当元素入队时,rear 加 1,而元素出队时,front 加 1,如下所示:

Python数据结构之队列详解

从以上示例中,可以很清楚地看到位于队头之前的空间被浪费了,为了解决这一问题,我们可以假设顺序队列为环状空间,将最后一个元素和第一个元素视为连续的,如下图所示:

Python数据结构之队列详解

从上图可以看出,当队列满时与队列空时,都有front=rear,因此无法通过 front==rear 来判断队列是否已满。针对这一问题,有两种解决方法:1) 设置标志来指示队列中是否还有空闲空间;2) 放弃一个元素空间,即当 front 指针在 rear 指针的下一位时 ((rear+1)%max_size=front) 队列为满。以下实现使用第二种方案。

同样顺序队列可以是固定长度和动态长度,当队列满时,定长顺序队列会抛出栈满异常,动态顺序队列则会动态申请空闲空间。

2.1.1 队列的初始化

顺序队列的初始化需要 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

2.1.2 求队列长度

由于 front 和 rear 分别用于记录队头元素和队尾元素的索引,因此我们可以方便的计算出队列的长度;同时我们需要考虑队列为循环队列,front 可能大于 rear,不能直接通过 rear-front,我们需要利用公式计算解决此问题:

Python数据结构之队列详解

Python 实现如下:

def size(self):
        return (self.rear-self.front+self.max_size) % self.max_size

2.1.3 判队列空

根据 front 和 rear 的值可以方便的判断队列是否为空:

def isempty(self):
        return self.rear==self.front

2.1.4 判队列满

根据 front 和 rear 的值可以方便的判断队列是否还有空余空间:

def isfull(self):
        return ((self.rear+1) % self.max_size == self.front)

2.1.5 入队

入队时,需要首先判断队列中是否还有空闲空间,然后根据队列为定长顺序队列或动态顺序队列,入队操作稍有不同:

[定长顺序队列的入队操作] 如果队满,则引发异常:

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)。这里需要注意的是,虽然当动态顺序队列满时,原队列中的元素需要首先复制到新队列中,然后添加新元素,但参考《顺序表及其操作实现》中顺序表追加操作的介绍,由于 n 次入队操作的总时间T(n) 与)O(n) 成正比,因此队栈的摊销时间复杂度可以认为是O(1)。

2.1.6 出队

若队列不空,则删除并返回队头元素:

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")

2.1.7 求队头元素

若队列不空,则返回队头元素:

def head(self):
        if not self.isempty():
            result = self.queue[self.front]
            return result
        else:
            raise IndexError("Empty Queue Exception")

2.2 链队列的实现

队列的另一种存储表示方式是使用链式存储结构,因此也常称为链队列,其中 enqueue 操作是通过在链表尾部插入元素来实现的,dequeue 操作是通过从头部删除节点来实现的。

2.2.1 队列结点

队列的结点实现与链表并无差别:

class Node:
    def __init__(self, data=None):
        self.data = data
        self.next = None
    def __str__(self):
        return str(self.data)

2.2.2 队列的初始化

队列的初始化函数中,使其队头指针 front 和 rear 均指向 None,并初始化队列长度:

class Queue:
    def __init__(self):
        self.front = None
        self.rear = None
        self.num = 0

2.2.3 求队列长度

返回 size 的值用于求取队列的长度,如果没有 size 属性,则需要遍历整个链表才能得到队列长度:

def size(self):
        return self.num

2.2.4 判队列空

根据队列的长度可以很容易的判断队列是否为空队列:

def isempty(self):
        return self.num <= 0

2.2.5 入队

入队时,在队尾插入新元素,并且需要将队尾指针 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

2.2.6 出队

若队列不空,则删除并返回队头元素,并且需要更新队头指针 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

2.2.7 求队头元素

若队列不空,则返回队头元素:

def head(self):
        if self.isempty():
            raise IndexError("Empty Queue Exception")
        result = self.front.data
        return result

2.3 队列的不同实现对比

队列的不同实现对比与栈的不同实现类似,可以参考《栈及其操作实现》。

3. 队列应用

接下来,我们首先测试上述实现的队列,以验证操作的有效性,然后利用实现的基本操作来解决实际算法问题。

3.1 顺序队列的应用

首先初始化一个顺序队列 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

3.2 链队列的应用

首先初始化一个链队列 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

3.3 利用队列基本操作实现复杂算法

考虑经典的约瑟夫斯环问题,n 个人围成一圈,从第 1 个人开始报数,第 m 个将被淘汰,重复上述过程,直到只剩下一个人,其余人都将被淘汰。

我们使用队列来模拟一个环,如下图所示,从队列的头部开始,将位于队首的人移出队列并立刻将其插入队列的尾部,之后此人会一直等待,直到其再次到达队列的头部。在出列和入列 m-1 次之后,位于队列头部的人出局(即第 m 个人),然后新一轮游戏开始;如此反复,直到队列中只剩下一个人(队列的大小为 1 )。

Python数据结构之队列详解

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

以上就是Python数据结构之队列详解的详细内容,更多关于Python队列的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
python中xrange和range的区别
May 13 Python
初步讲解Python中的元组概念
May 21 Python
Django中的forms组件实例详解
Nov 08 Python
Python判断一个文件夹内哪些文件是图片的实例
Dec 07 Python
python pandas库的安装和创建
Jan 10 Python
Python实现定期检查源目录与备份目录的差异并进行备份功能示例
Feb 27 Python
python之当你发现QTimer不能用时的解决方法
Jun 21 Python
Python使用matplotlib绘制Logistic曲线操作示例
Nov 28 Python
Python代码生成视频的缩略图的实例讲解
Dec 22 Python
python3 Scrapy爬虫框架ip代理配置的方法
Jan 17 Python
python GUI库图形界面开发之PyQt5布局控件QGridLayout详细使用方法与实例
Mar 06 Python
如何对python的字典进行排序
Jun 19 Python
Python学习之os包使用教程详解
分享几种python 变量合并方法
Mar 20 #Python
python 使用tkinter与messagebox写界面和弹窗
Mar 20 #Python
python中的sys模块和os模块
Mar 20 #Python
python_tkinter事件类型详情
Mar 20 #Python
再谈python_tkinter弹出对话框创建
python_tkinter弹出对话框创建
Mar 20 #Python
You might like
强烈推荐:php.ini中文版(2)
2006/10/09 PHP
执行、获取远程代码返回:file_get_contents 超时处理的问题详解
2013/06/25 PHP
PHP实现操作redis的封装类完整实例
2015/11/14 PHP
学习YUI.Ext第七日-View&amp;JSONView Part Two-一个画室网站的案例
2007/03/10 Javascript
Extjs学习笔记之三 extjs form更多的表单项
2010/01/07 Javascript
javascript匿名函数应用示例介绍
2014/03/07 Javascript
js实现按钮加背景图片常用方法
2014/11/01 Javascript
Javascript将数值转换为金额格式(分隔千分位和自动增加小数点)
2016/06/22 Javascript
AngularJs基于角色的前端访问控制的实现
2016/11/07 Javascript
angular.js 路由及页面传参示例
2017/02/24 Javascript
js正则表达式验证密码强度【推荐】
2017/03/03 Javascript
微信小程序getPhoneNumber获取用户手机号
2017/09/29 Javascript
jquery实现企业定位式导航效果
2018/01/01 jQuery
小程序视频或音频自定义可拖拽进度条的示例代码
2018/09/30 Javascript
JavaScript禁用右键单击优缺点分析
2019/01/20 Javascript
Vue 实现手动刷新组件的方法
2019/02/19 Javascript
windows下create-react-app 升级至3.3.1版本踩坑记
2020/02/17 Javascript
javascript局部自定义鼠标右键菜单
2020/12/08 Javascript
原生JS实现音乐播放器的示例代码
2021/02/25 Javascript
[02:46]解说DC:感谢430陪伴我们的DOTA2国际邀请赛岁月
2016/06/29 DOTA
Python使用pickle模块储存对象操作示例
2018/08/15 Python
利用python和ffmpeg 批量将其他图片转换为.yuv格式的方法
2019/01/08 Python
浅谈Python type的使用
2019/11/19 Python
PyCharm 2019.3发布增加了新功能一览
2019/12/08 Python
适合Python初学者的一些编程技巧
2020/02/12 Python
vscode写python时的代码错误提醒和自动格式化的方法
2020/05/07 Python
PyCharm2020最新激活码+激活码补丁(亲测最新版PyCharm2020.2激活成功)
2020/11/25 Python
python爬虫利用代理池更换IP的方法步骤
2021/02/21 Python
介绍CSS3使用技巧5个
2009/04/02 HTML / CSS
美国大型的健身社区和补充商店:Bodybuilding.com
2016/09/06 全球购物
中职生自荐信
2013/10/13 职场文书
水利学院求职自荐书
2014/02/01 职场文书
留学经费担保书
2014/05/12 职场文书
听证通知书
2015/04/24 职场文书
2019年最新证婚词精选集!
2019/06/28 职场文书
js中Map和Set的用法及区别实例详解
2022/02/15 Javascript