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 dict remove数组删除(del,pop)
Mar 24 Python
Python对象的深拷贝和浅拷贝详解
Aug 25 Python
python自动翻译实现方法
May 28 Python
Centos Python2 升级到Python3的简单实现
Jun 21 Python
django echarts饼图数据动态加载的实例
Aug 12 Python
python GUI库图形界面开发之PyQt5选项卡控件QTabWidget详细使用方法与实例
Mar 01 Python
Python基于Hypothesis测试库生成测试数据
Apr 29 Python
python为什么要安装到c盘
Jul 20 Python
Pycharm配置autopep8实现流程解析
Nov 28 Python
python 用Matplotlib作图中有多个Y轴
Nov 28 Python
用ldap作为django后端用户登录验证的实现
Dec 07 Python
django上传文件的三种方式
Apr 29 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 验证码的实现代码
2011/07/17 PHP
PHP网页游戏学习之Xnova(ogame)源码解读(十三)
2014/06/26 PHP
jQuery+PHP+ajax实现微博加载更多内容列表功能
2014/06/27 PHP
PHP中使用匿名函数操作数据库的例子
2014/11/17 PHP
php中time()与$_SERVER[REQUEST_TIME]用法区别
2014/11/19 PHP
php利用递归实现删除文件目录的方法
2016/09/23 PHP
php输出含有“#”字符串的方法
2017/01/18 PHP
firefox中用javascript实现鼠标位置的定位
2007/06/17 Javascript
JavaScript高级程序设计(第3版)学习笔记12 js正则表达式
2012/10/11 Javascript
jquery右下角自动弹出可关闭的广告层
2015/05/08 Javascript
初识Javascript小结
2015/07/16 Javascript
探究JavaScript函数式编程的乐趣
2015/12/14 Javascript
domReady的实现案例
2016/11/23 Javascript
微信小程序开发之选项卡(窗口底部TabBar)页面切换
2017/04/12 Javascript
Vant 中的Toast设置全局的延迟时间操作
2020/11/04 Javascript
Nest.js环境变量配置与序列化详解
2021/02/21 Javascript
[05:20]卡尔工作室_DOTA2新手教学_DOTA2超强新手功能
2013/04/22 DOTA
Python Socket使用实例
2017/12/18 Python
Django添加sitemap的方法示例
2018/08/06 Python
python绘制动态曲线教程
2020/02/24 Python
Python小白学习爬虫常用请求报头
2020/06/03 Python
C#如何判断当前用户是否输入某个域
2015/12/07 面试题
linux面试题参考答案(4)
2013/01/28 面试题
《最可爱的人》教学反思
2014/02/14 职场文书
小学生元旦感言
2014/02/26 职场文书
《秋姑娘的信》教学反思
2014/02/28 职场文书
党支部公开承诺书
2014/03/28 职场文书
项目合作意向书范本
2014/04/01 职场文书
庆国庆活动总结
2014/08/28 职场文书
街道务虚会发言材料
2014/10/20 职场文书
2014办公室年度工作总结
2014/12/09 职场文书
餐馆开业致辞
2015/08/01 职场文书
宾馆客房管理制度
2015/08/06 职场文书
如何用JavaScipt测网速
2021/05/09 Javascript
Nginx防盗链与服务优化配置的全过程
2022/01/18 Servers
gtx1650怎么样 gtx1650显卡相当于什么级别
2022/04/08 数码科技