什么数据结构与算法的概念、内容等基础性的内容网上太多了。为了让读者快速、深入理解Python常用数据结构作用及应用场景。
今天的文章正式开始Python数据结构与算法相关内容啦!
---队列篇:双端队列和一般的单端队列
从滑动窗口问题引出队列
示例中,从数组中第一个元素开始遍历,窗口大小设定为3,遍历到第三个元素时,窗口就形成;
之后,继续遍历元素时,为保持窗口大小固定,左侧元素需从窗口中删除,使得窗口一直向右移动。
从演示图可知,元素从窗口右侧入队,多余元素从窗口左侧出队,一端入队,一端出队,这不就是队列的性质吗?接下来介绍一下队列数据结构及特点。
队列大家应该不陌生,也是非常基础简单的数据结构。数据结构常用的操作一般为:「 增 」「 删 」「 改 」「 查 」,基本上所有的数据结构都是围绕这几个操作进行展开的。
先进先出队列可以类比我们核酸检测排队,前面的"先到先做"、"先进先出" 。
Queue 模块实现了三种类型的队列,它们的区别仅仅是队列中元素被取回的顺序。在 FIFO 队列中,先添加的任务先取回。在 LIFO 队列中,最近被添加的元素先取回(操作类似一个堆栈)。优先级队列中,元素将保持排序( 使用 heapq 模块 ) 并且最小值的条目第一个返回。
1、队列的定义
到底什么叫队列?
队列最显著的特点是先进先出(FIFO,First-In-First-Out)。限制仅一端进行插入操作,另一端进行删除操作的线性表。不允许数据直接插入队列,也不允许中间删除数据项。
队列的基础结构是一种线性表,具体应用中一般通过链表或数组来实现,只允许在队列尾部(称为rear)进行加入新元素操作,从队列头部(称为front)进行删除元素操作。front和rear两个指针,分别指向队列头 和队列尾 。
队列基本操作
队列的插入操作,叫做 入队。它是将 数据元素 从 队尾 进行插入的过程
队列的删除操作,叫做 出队。它是将 队首 元素进行删除的过程
队列的清空操作,就是一直出队,直到队列为空的过程,当 队首和队尾 重合时,就代表队尾为空了。
对于一个队列来说只能获取队首 数据 。
队列元素个数一般用一个额外变量存储,入队时加一,出队时减一。这样获取队列元素的时候就不需要遍历整个队列。
当队列元素个数为零时,就是一个空队,空队不允许出队操作。
2、代码实现
1)、利用列表实现一般的单端队列操作
考虑到 list 类型数据本身的存放就是有顺序的,而且内部元素又可以是各不相同的类型,基本上能够满足大多数的操作需求。
class Queue(object):
def __init__(self):
self.items = []
def __len__(self):
return len(self.items)
def is_empty(self):
return len(self.items) == 0
def add(self, value):
# 数据插入队列
return self.items.append(value)
# 出队
def delete(self):
if self.is_empty():
raise Exception("队列为空")
# pop是删除数据,默认删除最后一个
return self.items.pop(0)
# 输出队列数据
def show_queue(self):
print(self.items)
#或者通过继承列表改写
class Queue2(list):
#改写列表规则
def pop(self):
if len(self) == 0:
raise Exception("队列为空")
#继承后覆盖pop()方法
super(Queue2,self).pop(0)
q=Queue()
print("初始化队列",q.items)
print("队列是否为空","是" if q.is_empty() else "否")
for i in range(1,5):
q.add(i)
q.show_queue()
print("队列是否为空","是" if q.is_empty() else "否")
q.delete()
q.show_queue()
q.add(5)
q.show_queue()
q.delete()
q.delete()
q.delete()
q.show_queue()
初始化队列 []
队列是否为空 是
[1, 2, 3, 4]
队列是否为空 否
[2, 3, 4]
[2, 3, 4, 5]
[5]
2)、利用Python的自带的queue类模块实现队列操作
import queue
q = queue.Queue()
#方法介绍
# q.qsize() # 返回当前队列长度
# q.empty() # 判断队列是否为空,如果队列为空返回True 反之False
# q.full() # 判断队列是否达到最大长度限制,如果队列满了,返回True 反之False
# q.get() # 获取队列头部元素,get(self, block=True,timeout=None) block表示是否等待 timeout表示等待多久
# q.put(item) # 写入队列,尾部添加元素, put(self, item, block=True,timeout=None) block表示是否等待 timeout表示等待多久
# q.get_nowait() # 相当于q.get(False) 获取不等待
# q.put_nowait() # 相当于 q.put(item,False) 写入不等待,满了会报错
# q.task_done() # 在完成一项工作之后,使用这个方法可以向队列发送一个信号,表示该任务执行完毕
# q.join() # 等待队列中所有任务(数据)执行完毕之后再往下执行,否则一直等待
可以限制队列的长度,参数 maxsize 是一个整数,表示队列的最大长度;该参数默认为 0,即表示队列长度不限制,如果设置为负数,队列长度也无限(一般无人设置为负数)。
# 初始化队列:创建一个空队列
import queue
q1 = queue.Queue(maxsize=2) #参数 maxsize 是一个整数,表示队列的最大长度;该参数默认为 0,即表示队列长度不限制,如果设置为负数,队列长度也无限(一般无人设置为负数)。
print(type(q1))
# 入队:把数据添加到对位
# queue.Queue.put(item, [block[, timeout]])
# 其中item必填参数,即插入的值,block 默认为True;如果当block为True时且timeout是None(默认),put()方法就使调用线程阻塞在这里,直到空出一个数据单元。
q1.put(10,block=True) # 等待插入
q1.put(20,block=True)
q1.put(30,block=True)
print(q1)
# 如果 timeout 是个正数,将最多阻塞 timeout秒,如果在这段时间没有可用的空闲插槽,将引发 Full 异常。
q1.put(10,block=True) # 等待插入
q1.put(20,block=True)
q1.put(30,block=True,timeout=3)
print(q1)
q1.put(30,block=False) # 如果block是False,满了之后就报错,如果空闲立即可用,则把item放入队列,否则引发 Full异常 (在这种情况下,timeout 将被忽略)。
q1.put_nowait(30) # 不等待插入 满了之后报错
# 出队:从队首取数据
# Queue.get(block=True, timeout=None)
# 该函数表示从队列头部获取一个值,并在队列中删除该值,如果可选参数block是True并且timeout是None(默认值),则在必要时阻塞至可得到。
print(q1.get(block=True,timeout=None)) # 先入先出
print(q1.get(block=True,timeout=None))
print(q1.get(block=True,timeout=10)) # 如果timeout是个正数,将最多阻塞timeout秒,如果在这段时间内项目不能得到,将引发 Empty 异常。
print(q1.get(block=False)) # 不等待获取 队列为空报错
print(q1.get_nowait()) # 不等待获取 队列为空报错
print(q1.qsize()) # 获取队列中的任务数/消息数
print(q1.full()) # 判断队列是否已满
print(q1.empty()) # 判断队列是否为空
注:使用常见问题
1、阻塞模式
当队列q被(其他线程)写满后,这段代码就会阻塞,直至其他线程取走数据。
Queue.put()方法加上 block=False 的参数,即可解决这个隐蔽的问题。
但要注意,非阻塞方式写队列,当队列满时会抛出 exception Queue.Full的异常。
Queue.get()默认的也是阻塞方式读取数据;队列为空时,不会抛出except Queue.Empty,而是进入阻塞直至超时,因此得加上block=False的参数。
2)、用 collections.deque 来实现队列操作。
两端都能编辑,deque既可以用来实现栈也可以用来实现队列。
前面使用 list 实现队列的例子中,插入数据的部分是通过 insert() 方法实现的,这种方法效率并不高,因为每次从列表的开头插入一个数据,列表中所有元素都得向后移动一个位置。
from collections import deque
my_queue = deque(maxlen=8)
for i in range(8):
my_queue.append(i+1) # 在右侧插入新元素
# my_queue.appendleft(i+1) # 在左侧插入新元素
print(my_queue)
my_queue.rotate(2) #循环右移2次
print('循环右移2次后的队列',my_queue)
my_queue.popleft() #返回并删除队列最左端元素
print('删除最左端元素后的队列:',my_queue)
my_queue.pop() #返回并删除队列最右端元素
print('删除最右端元素后的队列:',my_queue)
# 现在我们来看一下如果继续向里面添加数字会发生什么。
for i in range(8, 12):
my_queue.append(i+1)
print(my_queue)
deque([1, 2, 3, 4, 5, 6, 7, 8], maxlen=8)
循环右移2次后的队列 deque([7, 8, 1, 2, 3, 4, 5, 6], maxlen=8)
删除最左端元素后的队列:deque([8, 1, 2, 3, 4, 5, 6], maxlen=8)
删除最右端元素后的队列:deque([8, 1, 2, 3, 4, 5], maxlen=8)
deque([2, 3, 4, 5, 9, 10, 11, 12], maxlen=8)
collections 库中的 deque 对该功能进行了优化。这个方法的一个关键特性是保持队列长度一直不变,也就是说,如果你将 queue 的最大大小设置为 8,那么 deque 将根据 FIFO 原则添加和删除元素,以保持 queue 的最大大小为 8。是不是有点固定窗口大小的的意思,这点非常有用。
3、队列的应用
给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口的滑动步长默认为1,滑动窗口每次只向右移动一位。返回滑动窗口中的业务处理结果。
实际问题:某电表设备正常表读数一般呈现递增状态,如果某时刻出现异常,如何识别出来叻?
【思路:我们可以利用滑动窗口来解决,窗口对应的数据结构为 双端队列 。默认移动窗口最大值时刻为窗口最大索引时刻值,若最大索引对应的值不等于窗口最大值,则表明设备该时刻出现异常。或者原序列与排序不相等,表明存在递减。】
这里仅结合业务背景简单演示队列应用。
from collections import deque
M=400 #平均值
C=20 #标准差(正常数据)
sw_width =7 #滑动窗口的窗口宽度
# input_data=[randint(M-2*C,M+5*C) for i in range(200)]
input_data = [100, 202,305, 437, 585, 598,601,701, 520, 981, 991, 1000, 1000, 1000, 1280, 1331, 725]
print("生成数据",input_data)
q = deque(maxlen=7)
# 单调递增序列
def is_monotonic(lst):
if any(lst[i+1] <= lst[i] for i in range(0,len(lst)-1)):
return False
else:
return True
for data in input_data:
q.append(data)
#队列长度是否达到要求
if len(q) == sw_width:
result=abs(sum(q)/sw_width-M)
print(q)
# 非单调递增
if is_monotonic(q) == False:
print("---------",is_monotonic(q))
print("机器出错", "错误数据为:", q)
#极值数据,平均值超过标准差3倍(跳表情况)
if result > 3*C and max(q)!=q[-1]:
print("异常值---------", q[-1])
print("平均值为:",result)
#数据恒定不变(掉线情况,更新失败)
elif len(set(q))<len(q):
from collections import Counter
b = dict(Counter(q))
print("存在重复数据",{key:value for key,value in b.items()if value > 1})
else:
print("数据合格")
本文分享自 Python数据分析实例 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!