Python数据结构与算法学习之双端队列
本篇文章给大家带来了关于python的相关知识,其中主要介绍了双端队列的相关问题,包括了双端队列的基本概念、双端队列的实现以及双端队列的应用,希望对大家有帮助。
推荐学习:python教程
0. 学习目标
双端队列是另一个线性数据结构。虽然它也是一种受限线性表,但与栈和队列不同的是,双端队列的限制很少,它的基本操作也是线性表操作的子集,但从数据类型的角度来讲,它们与线性表又有着巨大的不同。本节将介绍双端队列的定义及其不同实现,并且给出双端队列的一些实际应用。
通过本节学习,应掌握以下内容:
- 双端队列的基本概念及不同实现方法
- 双端队列基本操作的实现及时间复杂度
- 利用双端队列的基本操作实现复杂算法
1. 双端队列的基本概念
1.1 双端队列的基本概念
双端队列 (double-end queue
, deque
) 也是插入和删除操作分别被限制在序列两端的线性表,但与栈和队列不同的是,双端队列的限制很少,对于双端队列而言,队尾 (rear
) 和队头 (front
) 均允许插入元素和删除元素。新元素既可以被添加到队头, 也可以被添加到队尾。同理,已有的元素也能从任意一端移除。某种意义上,可以认为双端队列是栈和队列的结合。
尽管双端队列有栈和队列的很多特性,但是它并不要求按照这两种数据结构所限定的 LIFO
原则和 FIFO
原则操作元素。
1.2 双端队列抽象数据类型
除了添加和移除元素外,双端队列还具有初始化、判队空和求队长度等辅助操作。具体而言,双端队列的抽象数据类型定义如下:
基本操作:
1. __itit__(): 初始化双端队列
创建一个空双端队列
2. size(): 求取并返回双端队列中所含元素的个数 n
若双端队列为空,则返回整数0
3. isempty(): 判断是否为空双端队列
判断双端队列中是否存储元素
4. addfront(data): 双端队列队头添加元素
将元素 data 插入队头
5. addrear(data): 双端队列队尾添加元素
将元素 data 插入队尾
6. removefront(): 删除双端队列队头元素
删除并返回队头元素
7. removerear(): 删除双端队列队尾元素
删除并返回队尾元素
2. 双端队列的实现
和普通队列一样,双端队列同样有顺序存储和链式存储两种存储表示方式。
2.1 顺序双端队列的实现
类似于顺序队列,双端队列的顺序存储结构利用一组地址连续的存储单元依次存放从双端队列头到双端队列尾的元素,同时需要用两个指针 front
和 rear
分别指示队列头元素和队列尾元素的位置。初始化空双端队列时,front=rear=0
,当元素入队时,rear 加 1
,而元素出队时,front 加 1
,同时为了重复利用空闲空间,我们将顺序双端队列假设尾环状空间,最后一个空间和第一个空间视为连续空间(具体原因参考<顺序队列>):
同样顺序双端队列可以是固定长度和动态长度,当双端队列满时,定长顺序双端队列会抛出双端队列满异常,动态顺序双端队列则会动态申请空闲空间。
2.1.1 双端队列的初始化
顺序双端队列的初始化需要 4 部分信息:deque
列表用于存储数据元素,max_size
用于存储 queue
列表的最大长度,以及 front
和 rear
分别用于记录队头元素和队尾元素的索引:
class Deque: def __init__(self, max_size=6): self.max_size = max_size self.deque = [None] * self.max_size self.front = 0 self.rear = 0
2.1.2 求双端队列长度
由于 front
和 rear
分别用于记录队头元素和队尾元素的索引,因此我们可以方便的计算出双端队列的长度;同时我们需要考虑双端队列为循环队列,front
可能大于 rear
,不能直接通过 rear-front
,我们需要利用公式计算解决此问题:
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 addrear(self, data): if not self.isfull(): self.deque[self.rear] = data self.rear = (self.rear+1) % self.max_size else: raise IndexError("Full Deque Exception") def addfront(self, data): if self.isfull(): self.resize() if self.isempty(): # 当双端队列 self.deque[self.rear] = data self.rear = (self.rear+1) % self.max_size else: self.front = (self.front - 1 + self.max_size) % self.max_size self.deque[self.front] = data
[动态顺序双端队列的添加元素操作] 如果双端队列满,则首先申请新空间,然后再执行添加操作:
def resize(self): new_size = 2 * self.max_size new_deque = [None] * new_size d = new_size - self.max_size for i in range(self.max_size): new_deque[(self.front+i+d) % new_size] = self.deque[(self.front+i) % self.max_size] self.deque = new_deque self.front = (self.front+d) % new_size self.max_size = new_size # 注意队头和队尾修改索引的添加元素的不同顺序 def addrear(self, data): if self.isfull(): self.resize() self.deque[self.rear] = data self.rear = (self.rear+1) % self.max_size def addfront(self, data): if self.isfull(): self.resize() self.front = (self.front - 1 + self.max_size) % self.max_size self.deque[self.front] = data
与动态顺序队列类似,我们同样需要考虑复制之后的索引,否则可能出现存在不能用的空闲空间:
添加元素的时间复杂度为O(1)。虽然当动态顺序双端队列满时,原双端队列中的元素需要首先复制到新双端队列中,然后添加新元素,但参考《顺序表及其操作实现》中顺序表追加操作的介绍,由于 n
次添加元素操作的总时间T(n) 与O(n) 成正比,因此其摊销时间复杂度可以认为O(1)。
2.1.6 删除队头或队尾的元素
若双端队列不空,则删除并返回指定端元素:
# 注意队头和队尾修改索引的删除元素的不同顺序 def removefront(self): if not self.isempty(): result = self.deque[self.front] self.front = (self.front+1) % self.max_size return result else: raise IndexError("Empty Deque Exception") def removerear(self): if not self.isempty(): self.rear = (self.rear - 1 + self.max_size) % self.max_size result = self.deque[self.rear] return result else: raise IndexError("Empty Deque Exception")
2.2 链双端队列的实现
双端队列的另一种存储表示方式是使用链式存储结构,因此也常称为链双端队列,其中 addfront
操作和 addrear
操作分别是通过在链表头部和尾部插入元素来实现的,而 removefront
操作和 removerear
操作分别是通过从头部和尾部删除结点来实现的。为了降低在尾部删除结点的时间复杂度,接下来基于双向链表实现双端队列。
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 Deque: def __init__(self): self.front = None self.rear = None self.num = 0
2.2.3 求双端队列长度
返回 num
的值用于求取双端队列的长度,如果没有 num
属性,则需要遍历整个链表才能得到双端队列长度:
def size(self): return self.num
2.2.4 判双端队列空
根据双端队列的长度可以很容易的判断其是否为空双端队列:
def isempty(self): return self.num <= 0
2.2.5 添加元素
双端队列添加元素时,可以在队尾或队头插入新元素,因此需要修改 rear
和 front
指针,并且同时也要修改结点的 next
和 previous
指针;如果添加元素前双端队列为空,还需要进行相应处理:
def addrear(self, data): node = Node(data) # 如果添加元素前双端队列为空,则添加结点时,需要将front指针也指向该结点 if self.front is None: self.rear = node self.front = node else: node.previous = self.rear self.rear.next = node self.rear = node self.num += 1 def addfront(self, data): node = Node(data) # 如果添加元素前双端队列为空,则添加结点时,需要将rear指针也指向该结点 if self.rear is None: self.front = node self.rear = node else: node.next = self.front self.front.previous = node self.front = node self.num += 1
2.2.6 删除元素
若双端队列不空,可以从删除队头或队尾元素并返回,删除操作需要更新队头指针 front
以及尾指针 rear
,同时也要修改结点的 next
和 previous
指针,若出队元素尾队中最后一个结点,还需要进行相应处理:
def removefront(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 else: # 若删除操作完成后,双端队列不为空,将 front 指针的前驱指针指向 None self.front.previous = None return result def removerear(self): if self.isempty(): raise IndexError("Empty Queue Exception") result = self.rear.data self.rear = self.rear.previous self.num -= 1 if self.isempty(): self.front = self.rear else: # 若删除操作完成后,双端队列不为空,将 rear 指针的后继指针指向 None self.rear.next = None return result
2.3 双端队列的不同实现对比
双端队列的不同实现对比与栈的不同实现类似,可以参考《栈及其操作实现》。
3. 双端队列应用
接下来,我们首先测试上述实现的双端队列,以验证操作的有效性,然后利用实现的基本操作来解决实际算法问题。
3.1 顺序双端队列的应用
首先初始化一个顺序双端队列 deque
,然后测试相关操作:
# 初始化一个最大长度为5的双端队列dq = Deque(5)print('双端队列空?', dq.isempty())for i in range(3): print('队头添加元素:', 2*i) dq.addfront(2*i) print('队尾添加元素:', 2*i+1) dq.addrear(2*i+1)print('双端队列长度为:', dq.size())for i in range(3): print('队尾删除元素:', dq.removerear()) print('队头删除元素:', dq.removefront())print('双端队列长度为:', dq.size())
测试程序输出结果如下:
双端队列空? True队头添加元素: 0队尾添加元素: 1队头添加元素: 2队尾添加元素: 3队头添加元素: 4队尾添加元素: 5双端队列长度为: 6队尾删除元素: 5队头删除元素: 4队尾删除元素: 3队头删除元素: 2队尾删除元素: 1队头删除元素: 0双端队列长度为: 0
3.2 链双端队列的应用
首先初始化一个链双端队列 queue
,然后测试相关操作:
# 初始化新队列dq = Deque()print('双端队列空?', dq.isempty())for i in range(3): print('队头添加元素:', i) dq.addfront(2*i) print('队尾添加元素:', i+3) dq.addrear(2*i+1)print('双端队列长度为:', dq.size())for i in range(3): print('队尾删除元素:', dq.removerear()) print('队头删除元素:', dq.removefront())print('双端队列长度为:', dq.size())
测试程序输出结果如下:
双端队列空? True队头添加元素: 0队尾添加元素: 3队头添加元素: 1队尾添加元素: 4队头添加元素: 2队尾添加元素: 5双端队列长度为: 6队尾删除元素: 5队头删除元素: 4队尾删除元素: 3队头删除元素: 2队尾删除元素: 1队头删除元素: 0双端队列长度为: 0
3.3 利用双端队列基本操作实现复杂算法
[1] 给定一字符串 string
(如:abamaba),检查其是否为回文。
使用双端队列可以快速检查一字符串是否为回文序列,只需要将字符串中字符依次入队,然后从双端队列两端依次弹出元素,对比它们是否相等:
def ispalindrome(string): deque = Deque() for ch in string: deque.addfront(ch) flag = True while deque.size() > 1 and flag: ch1 = deque.removefront() ch2 = deque.removerear() if ch1 != ch2: flag = False return flag
验证算法有效性:
print('abcba是否为回文序列:', ispalindrome('abcba'))print('charaahc是否为回文序列:', ispalindrome('charaahc'))
结果输出如下:
abcba是否为回文序列: True charaahc是否为回文序列: False
推荐学习:python教程
以上是Python数据结构与算法学习之双端队列的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

热门话题

PHP和Python各有优劣,选择取决于项目需求和个人偏好。1.PHP适合快速开发和维护大型Web应用。2.Python在数据科学和机器学习领域占据主导地位。

Python和JavaScript在社区、库和资源方面的对比各有优劣。1)Python社区友好,适合初学者,但前端开发资源不如JavaScript丰富。2)Python在数据科学和机器学习库方面强大,JavaScript则在前端开发库和框架上更胜一筹。3)两者的学习资源都丰富,但Python适合从官方文档开始,JavaScript则以MDNWebDocs为佳。选择应基于项目需求和个人兴趣。

Docker利用Linux内核特性,提供高效、隔离的应用运行环境。其工作原理如下:1. 镜像作为只读模板,包含运行应用所需的一切;2. 联合文件系统(UnionFS)层叠多个文件系统,只存储差异部分,节省空间并加快速度;3. 守护进程管理镜像和容器,客户端用于交互;4. Namespaces和cgroups实现容器隔离和资源限制;5. 多种网络模式支持容器互联。理解这些核心概念,才能更好地利用Docker。

在 VS Code 中,可以通过以下步骤在终端运行程序:准备代码和打开集成终端确保代码目录与终端工作目录一致根据编程语言选择运行命令(如 Python 的 python your_file_name.py)检查是否成功运行并解决错误利用调试器提升调试效率

Python在自动化、脚本编写和任务管理中表现出色。1)自动化:通过标准库如os、shutil实现文件备份。2)脚本编写:使用psutil库监控系统资源。3)任务管理:利用schedule库调度任务。Python的易用性和丰富库支持使其在这些领域中成为首选工具。

VS Code 全称 Visual Studio Code,是一个由微软开发的免费开源跨平台代码编辑器和开发环境。它支持广泛的编程语言,提供语法高亮、代码自动补全、代码片段和智能提示等功能以提高开发效率。通过丰富的扩展生态系统,用户可以针对特定需求和语言添加扩展程序,例如调试器、代码格式化工具和 Git 集成。VS Code 还包含直观的调试器,有助于快速查找和解决代码中的 bug。

VS Code可以在Windows 8上运行,但体验可能不佳。首先确保系统已更新到最新补丁,然后下载与系统架构匹配的VS Code安装包,按照提示安装。安装后,注意某些扩展程序可能与Windows 8不兼容,需要寻找替代扩展或在虚拟机中使用更新的Windows系统。安装必要的扩展,检查是否正常工作。尽管VS Code在Windows 8上可行,但建议升级到更新的Windows系统以获得更好的开发体验和安全保障。

VS Code 可用于编写 Python,并提供许多功能,使其成为开发 Python 应用程序的理想工具。它允许用户:安装 Python 扩展,以获得代码补全、语法高亮和调试等功能。使用调试器逐步跟踪代码,查找和修复错误。集成 Git,进行版本控制。使用代码格式化工具,保持代码一致性。使用 Linting 工具,提前发现潜在问题。
