前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >学完数据结构,队列到底有什么用?

学完数据结构,队列到底有什么用?

作者头像
用户8949263
发布2022-11-07 12:54:48
1.1K0
发布2022-11-07 12:54:48
举报
文章被收录于专栏:Python数据分析实例

什么数据结构与算法的概念、内容等基础性的内容网上太多了。为了让读者快速、深入理解Python常用数据结构作用及应用场景。

今天的文章正式开始Python数据结构与算法相关内容啦!

---队列篇:双端队列和一般的单端队列

从滑动窗口问题引出队列

示例中,从数组中第一个元素开始遍历,窗口大小设定为3,遍历到第三个元素时,窗口就形成;

之后,继续遍历元素时,为保持窗口大小固定,左侧元素需从窗口中删除,使得窗口一直向右移动。

从演示图可知,元素从窗口右侧入队,多余元素从窗口左侧出队,一端入队,一端出队,这不就是队列的性质吗?接下来介绍一下队列数据结构及特点。

队列大家应该不陌生,也是非常基础简单的数据结构。数据结构常用的操作一般为:「 增 「 删 」「 改 」「 查 」,基本上所有的数据结构都是围绕这几个操作进行展开的。

先进先出队列可以类比我们核酸检测排队,前面的"先到先做"、"先进先出" 。

Queue 模块实现了三种类型的队列,它们的区别仅仅是队列中元素被取回的顺序。在 FIFO 队列中,先添加的任务先取回。在 LIFO 队列中,最近被添加的元素先取回(操作类似一个堆栈)。优先级队列中,元素将保持排序( 使用 heapq 模块 ) 并且最小值的条目第一个返回。

1、队列的定义

到底什么叫队列?

队列最显著的特点是先进先出(FIFO,First-In-First-Out)。限制仅一端进行插入操作,另一端进行删除操作的线性表。不允许数据直接插入队列,也不允许中间删除数据项。

队列的基础结构是一种线性表,具体应用中一般通过链表或数组来实现,只允许在队列尾部(称为rear)进行加入新元素操作,从队列头部(称为front)进行删除元素操作。front和rear两个指针,分别指向队列头 和队列尾 。

队列基本操作

1)数据入队

队列的插入操作,叫做 入队。它是将 数据元素 从 队尾 进行插入的过程

2)数据出队

 队列的删除操作,叫做 出队。它是将 队首 元素进行删除的过程

3)清空队列

队列的清空操作,就是一直出队,直到队列为空的过程,当 队首和队尾 重合时,就代表队尾为空了。

4)获取队首数据

对于一个队列来说只能获取队首 数据 。

5)获取队列元素个数

队列元素个数一般用一个额外变量存储,入队时加一,出队时减一。这样获取队列元素的时候就不需要遍历整个队列。

6)队列的判空

当队列元素个数为零时,就是一个空队,空队不允许出队操作。

2、代码实现

1)、利用列表实现一般的单端队列操作

考虑到 list 类型数据本身的存放就是有顺序的,而且内部元素又可以是各不相同的类型,基本上能够满足大多数的操作需求。

代码语言:javascript
复制
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()
代码语言:javascript
复制
初始化队列 []
队列是否为空 是
[1, 2, 3, 4]
队列是否为空 否
[2, 3, 4]
[2, 3, 4, 5]
[5]

2)、利用Python的自带的queue类模块实现队列操作

代码语言:javascript
复制
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,即表示队列长度不限制,如果设置为负数,队列长度也无限(一般无人设置为负数)。

代码语言:javascript
复制
# 初始化队列:创建一个空队列
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())  # 判断队列是否为空
代码语言:javascript
复制
注:使用常见问题
 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,滑动窗口每次只向右移动一位。返回滑动窗口中的业务处理结果。

实际问题:某电表设备正常表读数一般呈现递增状态,如果某时刻出现异常,如何识别出来叻?

思路:我们可以利用滑动窗口来解决,窗口对应的数据结构为 双端队列 。默认移动窗口最大值时刻为窗口最大索引时刻值,若最大索引对应的值不等于窗口最大值,则表明设备该时刻出现异常。或者原序列与排序不相等,表明存在递减。】

这里仅结合业务背景简单演示队列应用。

代码语言:javascript
复制

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("数据合格")
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-07-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Python数据分析实例 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1)数据入队
  • 2)数据出队
  • 3)清空队列
  • 4)获取队首数据
  • 5)获取队列元素个数
  • 6)队列的判空
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档