引子:
CPU任务调度处理的速度与线程数有关但非线性正相关,过多的线程反而会导致CPU频繁切换引起性能下降,所以我们需要选取特定大小的线程池。线程池的大小是根据如任务特点等软硬件环境来设置的。
从线程池申请线程时会遇到没有空闲资源需排队等候的情况,处理这种情况会用到“队列(queue)”这种底层数据结构。
队列(queue)
队列的数据结构特点和基本操作是?
数据结构特点:先进先出,后进后出。
基本操作:入队(enqueue)–添加数据到队尾;出队(dequeue)–从对头取出数据。跟栈一样也是一种操作受限的的线性表数据结构。
与栈对比:栈是先进后出后进先出,拥有入栈和出栈两个操作。队列与栈的一个很大的区别是,栈的数据出入只有一个口,而队列有两个数据口(一个进,一个出)。
与栈一样,由数组组成的队列称为顺序队列,由链表组成的队列称为链式队列。
顺序队列
话不多说,show you the code:
"""
Discription:
Array Queue
obj.capacity get queue capacity
obj.head get queue index head
obj.tail get queue index tail
enqueue()
dequeue()
print_all()
Author: calm.xia@gmail.com
"""
import sys
class ArrayQueue(object):
def __init__(self, capacity: int):
self.__arrayqueue = [None] * capacity # Here can not work with self.__arrayqueue.append(item)
self.__capacity = capacity
self.__head = 0
self.__tail = 0
...
def enqueue(self, item: int):
"""
Return:
False fail
True sucess
"""
if self.__tail == self.__capacity :
return False
self.__arrayqueue[self.__tail] = item
self.__tail += 1
if self.__tail == self.__capacity :
print("queue is full!")
return True
def dequeue(self):
'''
Return:
None fail
item success
'''
print("dequeue... head %d tail %d"%(self.__head, self.__tail))
if self.__head == self.__tail :
print("Aarray queue is NULL!")
return
#item = self.__arrayqueue.pop(self.__head)
item = self.__arrayqueue[self.__head]
self.__arrayqueue[self.__head] = None
self.__head += 1
return item
...
含测试代码的详细代码见 github:ArrayQueue.py
贴一张队满时候的图,注意,队满时 tail 所指向的下表并没有存储元素。
问题来了:当 tail 达到队列 capacity(队列大小)处,且 head 并不等于0,又有新元素入队时,怎么办?
在这里,出队并没有影响,仅仅是入队的时候需要进行处理,这里有两种方案:
- 方案一:每次出队都是取出 index 0的元素,那么在每次出队操作完后都将剩余队列数据整体往左搬移填满 index 0。这是最容易想到的办法,但是出队操作一频繁,这样的操作时间复杂度达到O(n),有没有更好的办法?
- 方案二:集中搬移。当 tail 达到队列 capacity(队列大小)处,且 head 不等于0,又有新元素入队时,触发一次整体数据搬移,其他时候数据不进行搬移。比如,head不等于0,tail 也未达到 capacity时,并不触发数据搬移操作。这样可以大大降低搬移数据的次数,降低操作的时间复杂度。整体过程如下图:
show you the code:
def enqueue(self, item: int):
"""
Return:
False fail
True sucess
"""
print("enqueue... head %d tail %d"%(self.__head, self.__tail))
if self.__tail == self.__capacity :
if self.__head == 0 :
print("enqueue error: queue is full ")
return False
else:
print("enqueue warn: migrate queue data...")
for i in range(self.__head, self.__tail):
self.__arrayqueue[i - self.__head] = self.__arrayqueue[i]
self.__arrayqueue[i] = None
self.__tail = self.__tail - self.__head
self.__head = 0
#print("enqueue 002... head %d tail %d"%(self.__head, self.__tail))
self.__arrayqueue[self.__tail] = item
#self.__arrayqueue.append(item)
self.__tail += 1
if self.__tail == self.__capacity :
print("queue is full!")
return True
链式队列
Todo
循环队列
上面顺序队列中提到的 tail == capacity 时数据搬移的问题能否优化避免呢?答案是可以的,用到的就是循环队列。
将顺序队列首尾相连即可形成循环队列。
只要head和tail不交叉,tail可以一直递增下去,直到队列满;当队列满时,出队之后,可以继续入队,从而不用进行数据搬移。这依赖于循环队列的一个关键:确定队空和队满的判定条件
。条件具体是?先上图,可以通过枚举的方式找出规律再验证。
答案是:(tail + 1) % capacity = head。其中capacity为队列大小。
话不多说,上代码:
"""
Discription:
Circular Queue
obj.capacity get queue capacity
obj.head get queue index head
obj.tail get queue index tail
enqueue()
dequeue()
print_all()
Author: calm.xia@gmail.com
"""
import sys
class CircularQueue(object):
def __init__(self, capacity: int):
self.__circularqueue = [None] * (capacity)
self.__capacity = capacity
self.__head = 0
self.__tail = 0
...
def enqueue(self, item: int):
"""
Return:
False fail
True sucess
"""
print("enqueue... head %d tail %d"%(self.__head, self.__tail))
# Actually, the real capacity is self.__capacity-1. Circular queue will waste a element space
if (self.__tail + 1) % self.__capacity == self.__head :
print("enqueue error: circular queue is full ")
return False
else:
self.__circularqueue[self.__tail] = item
#self.__circularqueue[i] = None
self.__tail = (self.__tail + 1) % self.__capacity
return True
def dequeue(self):
'''
Return:
None fail
item success
'''
print("dequeue... head %d tail %d"%(self.__head, self.__tail))
if self.__head == self.__tail :
print("Acircular queue is NULL!")
return
#item = self.__circularqueue.pop(self.__head)
item = self.__circularqueue[self.__head]
self.__circularqueue[self.__head] = None
self.__head = (self.__head +1) % self.__capacity
return item
详细代码参看 github:ArrayQueue.py
注意:循环队列队满时会浪费一个元素的存储空间,这点需要注意。
预告:下一篇《数据结构与算法 – 09 队列 | 实际应用》将介绍一些队列的实际应用。你可以想到一些吗?欢迎大家交流。