概念:
队列:
一种先进先出(FIFO)的抽象数据模型,类似于大家去看电影购票排队一样,先排到的先进,后来的排在结尾,来的越晚,进去看电影也就越晚。所以里边就有两个最基础的方法,出队列和进队列。
出队列:位于队列开头的元素离开队列(弹出)
进队列:插入队列结尾
注意:
队列虽然动态扩容,又因为我们利用列表实现,故很多同学在这里会觉得队列的实现也不难,只需要完成队列元素弹出(类似于列表结构的pop(0))和队列元素的添加(类似于列表结构的append(variable)),有没有错呢?只能说没错,但是请各位记住,越简单的越低效(这就是为什么有很多同学在学习C语言时候觉得枯燥乏味,很难的原因,因为C很高效呗,至少我是这样认为。)有一个很简单的逻辑,大家想想看,虽然弹出了队列最前端的元素,但是这个会留下一个‘洞’,随着添加的元素越来越多,弹出的元素越来越多时,你会发现,这个些‘洞’越来越大,这是不是一个潜在的问题呢?是的它会影响到执行效率(从列表中删除一个元素是一种比较复杂的底层实现,相对来说消耗更长的时间,或者说占用资源的次数更多,导致效率低下,在解决方法中提出了其的替代方案,这种方法优于pop)。
解决方法:
有同学时候遇到这个洞我要怎么办?办法自然是给它补上。应对上边的注意,我们完全可以避开使用pop(0)这种有‘漏洞’的方法,可以用一个只带为空的指针(这里可以用python中的None)代替这个数组中离去的元素。并且利用一个现实的变量front来保存当前在最前面的元素的索引(下标)。经过几次离队操作后,队列可能变成了如下的样子:
(作者自己亲手画的,大家凑合看吧,我这个最丑板书那是出了名的 …)
不幸地是,它也有缺点,在前一节,我们讲解了用列表实现栈,那个时候的列表长度就是栈的大小(甚至列表略大)。对于队列的设计上,情况不尽如人意,随着时间的推移,不断向队列中添加新元素,通过上图各位也应该能发现,底层列表的大小将逐渐增长到O(m),然而m不是列表中元素的个数,而是自队列创建以来所有对列表进行追加操作的操作的数量总和,这样在某些操作中将会造成很不利的影响,例如:餐厅现在排队的人数可能只有十个人,但是一天排队的总人数却远远大于了这个值。
解决方案优化:循环使用数组
我们队列的前端趋向右端,同时让队列内的元素在队列结尾处‘循环’,假设底层列表的长度为L,实际上L期望一定大于实际队列中元素的个数,性的元素在当前队列的尾部利用队列操作进入队列,逐步将元素从队列的前面插入索引为L-1的位置,然后紧接着是索引为0的位置,接着是索引为1的位置(注意,索引就是下标的意思,是个名词,别问我下标索引是什么意思,这是动词,自己体会)。给大家一个实际的分析过后的例子:
python队列的实现方法:
_list_que:底层列表,且固定长度为len(_list_queue)
_counter:实际列表中的元素个数(队列元素的个数)
_front:前边一直在提的队列当中第一个元素的索引
尽管通常队列的初始大小为0,但是列表有一个固定的能够存储中等长度数据的长度的,记得要将队列的第一个元素的索引初始化为0。
当队列为空时,出队列和索引被调用时会抛出异常告知队列为空。
以下便是代码实现了:
class ArrayQue:
DEFAULT_LENGTH = 10
def __init__(self):
self._list_que = [None]* ArrayQue.DEFAULT_LENGTH
self._counter = 0
self._front = 0
def __len__(self):
return self._counter
def is_empty(self):
if self._counter == 0:
return True
else:
return False
def top(self):# 返回队列顶
if self.is_empty():
raise TypeError('Queue is empty!')
return self._list_que[self._front]
def dequeue(self):# 离开队列:上述图片中有解释,结合着看
if self.is_empty():
raise TypeError('Queue is empty!')
answer = self._list_que[self._front]
self._list_que[self._front] = None
self._front = (self._front + 1) % len(self._list_que)
self._counter -= 1
if self._counter < len(self._list_que) // 4:
# 动态缩容:算法提炼:当元素总个数小于列表长度的1/4时,将列表长度缩减到目前长度的1/2
self._resize(len(self._list_que)//2)
return answer
def enqueue(self,e):# 入队列
if self._counter == len(self._list_que):
self._resize(2*len(self._list_que))
avail = (self._front + self._counter) % len(self._list_que)
self._list_que[avail] = e
self._counter += 1
def _resize(self,var_new_len):# 动态扩容的实现
old = self._list_que# 保留原列表
self._list_que = [None] * var_new_len# 初始化原是列表为新长度列表
walk = self._front# 保留原有的队列第一个元素的索引
for k in range(self._counter): # 索引从0~队列元素个数-1(即已有元素的所有索引的遍历)为止
self._list_que[k] = old[walk]# 将老列表中对应第一个索引开始的元素放在新列表其实第一个位置上,以此类推
walk = (walk + 1) % len(old)# 队列对应老列表中的元素的索引递增1,为什么取模?很简单,这样刚好可以使索引循环
self._front = 0# 新队列中,索引初始化为队列开头
想要了解更多有趣内容,请关注‘小白piao学python’(会有不定期的公开课哦),如果想要咨询学习python的小技巧,可以联系小白piao的个人微信:wxi_xbp_python3。
公众号: