目录
1 队列概览
- 队列是线性的集合
- 队列的插入限制在队尾,删除限制在队头。支持先进先出协议( FIFIO, first-in first-out )
- 两个基本操作
- add:在队尾添加一项
- pop:从队头弹出一项
- 优先集合:
- 在优先队列中,具有较高优先级的项,会在那些具有较低优先级的项之前弹出。具有相同优先级的项,则仍然按照 FIFO的顺序弹出
- 计算机科学中的大多数队列,涉及对共享资源的访问
- CPU访问——进程排队等待访问一个共享的CPU
- 磁盘访问——进程排队等待访问共享的辅助存储设备
- 打印机访问——打印任务排队等待访问共享的激光打印机
2 队列接口
- Python程序员可以使用一个Python列表来模拟队列。最简单的策略是使用列表的append方法来把项添加到队列的队尾,并且使用pop(0)方法从列表的前面删除项并返回它
- 该方法的主要缺点是:所有其他的列表操作也都可以操作队列。这包括在任何的位置插入、替换和删除一项。这些操作违反了队列作为一种抽象数据模型的本意
队列方法 |
作用 |
q.isEmpty() | 如果q为空返回True,否则返回False |
__len__(q) | 和len(q)相同,返回q中的项的数目 |
__str__(q) | 和str(q)相同,返回q的字符串表示 |
q.__iter__() | 和iter(q)或for item in q:相同;从前到后,访问q中的每一项 |
q.__contains__(item) | 和item in q相同,如果item在q中,返回True,否则返回False |
q1.__add__(q2) | 和q1+q2相同,返回一个新的队列,其中包含了q1和q2中的项 |
q.__eq__(anyObject) | 和q==anyObject相同,如果q等于anyObject,返回True,否则返回False。如果队列中对应位置的项都是相同的,则两个队列相等 |
q.clear() | 将q清空 |
q.peek() | 返回q队头的项。先验条件:q不能为空,如果队列为空的话,将会抛出一个KeyError |
q.add(item) | 将item添加到q的队尾 |
q.pop() | 删除并返回q队头的项。先验条件:q不能为空。如果队列为空的话,将会抛出一个KeyError |
- pop和peek有一个重要的先验条件,并且如果队列的用户不能满足这个先验条件的话,将会引发一个异常
操作 | 该操作之后栈的状态 | 返回值 | 说明 |
Q = <Queue Type>() | 初始化,队列为空 | ||
q.add(a) | a | 队列只包含一个项a | |
q.add(b) | a b | a位于队列队头,b位于队列队尾 | |
q.add(c) | a b c | c位于队列队尾 |
|
q.isEmpty() | a b c | False | 这个队列不为空 |
len(q) | a b c | 3 | 队列中包含3个项 |
q.peek() | a b c | a | 返回队头的值,但并不删除它 |
q.pop() | b c | a | 删除并返回队头的值a,b现在成为队头 |
q.pop() | c | b | 删除并返回队头的值b,c现在成为栈顶 |
q.pop() | c | 删除并返回队头的值c | |
q.isEmpty() | True | 队列现在为空 | |
q.peek() | KeyError | 查看一个空的队列会导致一个异常 | |
q.pop() | KeyError | 弹出一个空的队列会导致一个异常 |
|
q.add(d) | d | d是队头的项 |
3 队列的2种应用
- 队列的两个应用,一个设计计算机模拟,另一个设计CPU调度的轮询。
3.1 模拟
- 确定每天不同时间段,在岗工作的收很员的数目。这种情况下,一些重要的因素如下:
- 新增顾客的频率
- 可用的收银员的数目
- 顾客的购物车中的商品的数目
- 需要考虑的时间段
- 这些因素也可以输入到一个模拟程序中,程序随后确定要接待的顾客的数目、每位顾客等待服务的平均时间,以及在模拟时间段的最后还在排队的顾客的数目。通过改变输入,特别是顾客到达的频率和可用的收银员的数目,模拟程序就能够帮助经理在每天的繁忙时段和空闲时段做出有效的人手调配决策。通过添加一个输入来量化不同的结账设备的效率,经理甚至可以判断增加更多的收银员还是购买更多、更好的高效设备更划算。
3.2 轮询CPU调度
- 轮训调度,就是将新的进程添加到一个等待队列的队尾,这个队列包含了等待使用的CPU的进程
- 等待队列中的每一个进程都会依次弹出,并且会给予CPU时间的一个分片。当时间分片用完的时候,该进程就会返回到队列的队尾
- 示意图
- 通常,并不是所有的进程需要CPU的紧急程度都是相同的。例如,用户对于计算机的满意度,在很大程度上受到了计算机对键盘和鼠标输入的响应时间的影响。因此,优先响应处理这些输入的进程是有意义的
- 轮训调度使用优先队列并且给每个进程分配一个合适的优先级,从而满足这种需求
4 队列的实现
4.1 队列的链表实现
- 为了实现对队列两端的快速访问,需要指向两端的外部指针
- add操作:程序创建了一个新的节点,并将最后一个节点的next指针设置为这个新的节点,并且将变量rear设置为这个新节点
- add方法代码:
def add(self, newItem):
newNode = Node(newItem, None)
if self.isEmpty():
self._front = newNode
else:
self._rear.next = newNode
self._rear = newNode
self._size += 1
- pop操作:在一次弹出操作之后,队列变为空,那么必须将front和rear指针都设置为None
- pop方法代码:
def pop(self):
"""Removes and returns the item at front of the queue.
Precondition: the queue is not empty."""
# Check Precondition here
if self.isEmpty():
raise KeyError("The queue is empty")
oldItem = self._front.data
self._front = self._front.next
if self._front is None:
self._rear = None
self._size -=1
return oldItem
4.2 队列的数组实现
- 使用 front 和 rear 两个索引来指示队列的开始和结束位置
- 调整数组大小时,最好让数组占据最初的数组段
4.3 两种实现的时间和空间复杂度分析
- __str__,__add__ 和 __eq__方法的运行时间是 O(n),其他方法的最大运行时间为O(1)。但是数组实现,在需要调整数组大小时,其运行时间为O(n)
- 当数组实现的填充因子大于 1/2 时,其内存利用率要比链表实现高,对于小于1/2的装载因子,数