Python模拟 栈 队列 以及优先队列的操作记录
栈:数据是后进先出 (LIFO) last in first out
队列:数据是先进先出 (FIFO) first in first out
第一种就是列表:(既可以模拟栈也可以模拟队列)一好一差。(还有一个缺点,不能通过简单的方式,设置容器容量的空间。)
列表是一种容器序列,而且在模拟栈操作的时候,速度也是比较可以的。
在模拟栈操作时,必须用append于pop。(模拟栈的时候,性能优秀)
下面演示的是模拟队列操作,模拟栈操作很简单,就是append于pop所以不展示了。
In [228]: ll = list() In [229]: ll.append('任务1') In [230]: ll.append('任务2') In [231]: ll.append('任务3') In [232]: ll.pop(0) Out[232]: '任务1' In [233]: ll.pop(0) Out[233]: '任务2' In [234]: ll.pop(0) Out[234]: '任务3'
这样的模拟操作队列效率是非常低的,因为你每次移出第一个元素,后面的元素索引都会发生变化
专业的说法需要的时间为O(n)
第二个为collections.deque,这个一个模拟栈于队列都非常优秀的容器序列。(可以设置容器容量大小,当超过最大容量时,最先进去的元素将被踢出容器)
除了随机访问该对象中间的元素的性能很差,耗时为O(n),但一般操作容器序列,很少取访问中间元素。
模拟栈操作:
from collections import deque In [34]: deque? Init signature: deque(self, /, *args, **kwargs) Docstring: deque([iterable[, maxlen]]) --> deque object A list-like sequence optimized for data accesses near its endpoints. File: /usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/collections/__init__.py Type: type Subclasses: In [35]: d = deque(range(10),maxlen=5) In [36]: d Out[36]: deque([5, 6, 7, 8, 9]) In [37]: d.append(10) In [38]: d Out[38]: deque([6, 7, 8, 9, 10]) In [39]: d.pop() Out[39]: 10 In [40]: d.pop() Out[40]: 9 In [41]: d.pop() Out[41]: 8 In [42]: d.pop() Out[42]: 7 In [43]: d.pop() Out[43]: 6 In [44]: d.pop() --------------------------------------------------------------------------- IndexError Traceback (most recent call last) <ipython-input-44-663961784a31> in <module> ----> 1 d.pop() IndexError: pop from an empty deque
模拟队列操作:
d Out[45]: deque([]) In [46]: d.append(1) In [47]: d.append(2) In [48]: d.append(3) In [49]: a --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-49-3f786850e387> in <module> ----> 1 a NameError: name 'a' is not defined In [50]: d Out[50]: deque([1, 2, 3]) In [51]: d.popleft() Out[51]: 1 In [52]: d.popleft() Out[52]: 2 In [53]: d.popleft() Out[53]: 3
上面两个是即可以模拟栈,又可以模拟队列的容器序列,下面介绍的是只能单一模拟栈或者队列的。
queue.LifoQueue从字面就可以看出来是模拟栈的,后进先出。queue模块下的容器序列,提供了锁语句来支持多个并发的生产者于消费者。模块下面的很多类的实例一般用在线程间的通讯。
因为这个容器在取不到元素或者元素的数量超过设置容量时,会阻塞程序。(所以很明显这个是可以设置容量的)
In [54]: from queue import LifoQueue In [55]: LifoQueue? Init signature: LifoQueue(maxsize=0) Docstring: Variant of Queue that retrieves most recently added entries first. File: /usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/queue.py Type: type Subclasses: In [56]: q = LifoQueue() In [57]: q = LifoQueue(maxsize=3) In [58]: q.put(1) In [59]: q.put(2) In [60]: q.put(3) In [61]: q.get() Out[61]: 3 In [62]: q.get() Out[62]: 2 In [63]: q.get() Out[63]: 1 In [64]: q.get() 阻塞
栈的我了解的就是上面三种形式,一般优先使用collections.queue
后面的单一功能主要队列的序列容器。
queuq.Queue跟前面的LifoQueue功能于操作都差不多,而且都是用线程之间的任务通讯操作。
In [94]: q = Queue(maxsize=3) In [95]: q.put(1) In [96]: q.put(2) In [97]: q.put(3) In [98]: q.get() Out[98]: 1 In [99]: q.get() Out[99]: 2 In [100]: q.get() Out[100]: 3 In [101]: q.get()
阻塞
最后还有一个multiprocessing.Queue进行作业队列。
multiprocessing.Queue主要是进程间进行作业通讯的工具,具体使用基本于
queue.Queue差不多,就不上代码了。
最后简单的记录一下优先队列。
优先队列史一个容器数据结构,使用具有全序关系的键来管理元素,以便快速访问容器中键值最小或者最大的元素。
1、列表,手动维护有序队列。
In [102]: q = [] In [103]: q.append((2, 'code')) In [104]: q.append((1,'eat')) In [105]: q.append((3,'sleep')) In [106]: q.sort(reverse=True) In [107]: q.pop() Out[107]: (1, 'eat') In [108]: q.pop() Out[108]: (2, 'code') In [109]: q.pop() Out[109]: (3, 'sleep') In [110]: import bisect In [111]: bisect.insort(q,(2, 'code')) In [112]: bisect.insort(q,(1, 'eat')) In [113]: bisect.insort(q,(3, 'sleep')) In [114]: q Out[114]: [(1, 'eat'), (2, 'code'), (3, 'sleep')] In [115]:
bisect在已经排序完成的情况下,查寻索引非常快的,bisect.bisect可以查寻索引。比默认的.index快太多了。
heapq--基于列表的二叉堆。
heaqp是二叉堆,通常用普通列表实现,能在O(logn)时间内插入和获取最小的元素。
heapq模块是在Python中不错的优先级队列实现。由于heapq的技术上只提供最小堆实现,因此必须添加额外步骤来确保排序的稳定性,以此来获得实际的优先级队列中所含有的特性。
In [140]: ...: q = [(2, 'code'), (3, 'sleep'), (1, 'eat'), (4,'drink')] ...: In [141]: random.shuffle(q) In [142]: h_q = [] In [143]: for i in q: ...: heapq.heappush(h_q, i) ...: In [144]: heapq.heappop(h_q) Out[144]: (1, 'eat') In [145]: heapq.heappop(h_q) Out[145]: (2, 'code') In [146]: heapq.heappop(h_q) Out[146]: (3, 'sleep') In [147]: heapq.heappop(h_q) Out[147]: (4, 'drink') In [148]: q = [(2, 'code'), (3, 'sleep'), (1, 'eat'), (4,'drink')] In [149]: random.shuffle(q) In [150]: heapq.heapify(q) In [151]: heapq.heappop(q) Out[151]: (1, 'eat') In [152]: heapq.heappop(q) Out[152]: (2, 'code') In [153]: heapq.heappop(q) Out[153]: (3, 'sleep') In [154]: heapq.heappop(q) Out[154]: (4, 'drink')
上面用了heapq的两种方式堆列表进行了二叉堆操作。第一种是变量元素用heapq.heappush进行数据加载,第二种直接用heapq.heapify对列表元素实行二叉堆。
heapq.nlargest,heapq.nsmallest可以找出容器元素内的最大值范围于最小值范围,通过key=函数,传递函数。
from queue import PriorityQueue In [163]: PriorityQueue? Init signature: PriorityQueue(maxsize=0) Docstring: Variant of Queue that retrieves open entries in priority order (lowest first). Entries are typically tuples of the form: (priority number, data). File: /usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/queue.py Type: type Subclasses: In [164]: q = [(2, 'code'), (3, 'sleep'), (1, 'eat'), (4,'drink')] In [165]: p_q = PriorityQueue() In [166]: for i in q: ...: p_q.put(i) ...: In [171]: p_q.empty Out[171]: <bound method Queue.empty of <queue.PriorityQueue object at 0x10b5e2b90>> In [172]: p_q.empty() Out[172]: False In [173]: while not p_q.empty(): ...: print(p_q.get()) ...: ...: (1, 'eat') (2, 'code') (3, 'sleep') (4, 'drink') In [174]:
queue.PriorityQueue内部也是使用了heapq,时间复杂度于空间复杂度于heapq相同,但PriorityQueue队列执行并发生产者消费者。可以在特定条件下阻塞。
queue.PriorityQueue可以给nametuple对象进行优先级压入弹出,弹出参数按照nametuple对象第0个元素的值从小到大进行弹出,本质上来说,nametuple就是元祖。
In [23]: from queue import PriorityQueue In [24]: q = PriorityQueue() In [25]: car = namedtuple('car','number,colour') In [26]: car1 = car(3,'red') In [27]: car2 = car(1,'yellow') In [28]: car3 = car(2,'gren') In [29]: q.put(car1) In [30]: q.put(car2) In [31]: q.put(car3) In [32]: q.get() Out[32]: car(number=1, colour='yellow') In [33]: q.get() Out[33]: car(number=2, colour='gren') In [34]: q.get() Out[34]: car(number=3, colour='red') In [35]: q.get()
queue里面的三个模块基本在模拟队列,栈,于优先级队列中都又使用。
分别是queue.LifoQueue,queue.Queue,queue.PriorityQueue。
Python前面记录的三种优先队列,其中priorityQueue是其中的首选,具有良好的面向对象的接口
如果想避免PriorityQueue的锁开销,建议直接用heapq模块。
2021年1月21日更新:
queue的一些有趣玩法,主要是一个rotate还有就是,collections.queue更像一个加强版的list
参考官方链接:https://docs.python.org/zh-cn/3/library/collections.html?highlight=deque#collections.deque.rotate
这一节展示了deque的多种用法。
collestion.deque就是加强版的list,list能实现的功能,它都能实现。
限长deque提供了类似Unix tail
过滤功能
def tail(filename, n=10): 'Return the last n lines of a file' with open(filename) as f: return deque(f, n)
一个 轮询调度器 可以通过在 deque
中放入迭代器来实现。值从当前迭代器的位置0被取出并暂存(yield)。 如果这个迭代器消耗完毕,就用 popleft()
将其从对列中移去;否则,就通过 rotate()
将它移到队列的末尾
def roundrobin(*iterables): "roundrobin('ABC', 'D', 'EF') --> A D E B F C" iterators = deque(map(iter, iterables)) while iterators: try: while True: yield next(iterators[0]) iterators.rotate(-1) except StopIteration: # Remove an exhausted iterator. iterators.popleft()
这是rotate的使用确实让我感觉非常有意思,且非常使用。要是自己写,确实让人伤脑筋。