前言:此篇只为本人小菜鸟的练习,如有错误,还烦请大家指出。
自设队列
队列同样作为一个简单的线性数据结构,用来存储不同的数据,其原则与栈相反,严格遵守了first in last out(filo)原则,简单可以理解为子弹,排队等模型,其并不能像栈那样仅仅使用python内部列表的特性去完成filo的队列(原因很简单,如果我们需要出队列元素时,很明显我们需要将第一个删除,那么第一个后面所有的元素都会挪动,这样的效率实在太低了),因此我们采用环形列表来实现队列。
此环形并非物理结构上的环形,而是逻辑结构,具体实现环形我们采用取余的方法,下文会有详细说明,此外,队列与栈还有一个不同那便是大小的约束,实现栈时,其大小一般初始化为0,随着插入和删除大小发生变化,但是队列一开始是有大小的规定的,即初始化时就要指定队列的大小。
附上一张环形队列的草图。
由于队列存在大小的限制,那边多出了许多不同于栈的操作,自动增容,判断是否队列已满等等。
因此,我们需要区分出判满和判空的操作(由于初始化规定大小,所以队内还要多少是不清楚的),为此我们牺牲了队列中的一个位置,定义出两个指针front和rear,rear随着增加元素而移动,先移动后增加,而front随着删除移动,先移动后删除,从始至终我们都要保证front指向的位置没有元素,并不是真的没有元素,而是front指向的元素并不属于队内!这样就可以区分开判满和判空,即若front 等于rear就是空,若rear+1等于front就是满。
交代完这些要点就开始用类实现一个环形队列吧。
class Queue:
def __init__(self,size):
self.size = size # 指定队列规模
self.queue = [0 for _ in range(self.size)]
self.rear = 0 # 增加元素指针
self.front = 0 # 删除指针
rear和front都初始化为0,传入的参数size是指定队列的规模大小,至此开始队列的一些操作。
回到前文提到的环形队列采用取余的方法实现的意思是对于超出队列大小的下标我们对其队列大小进行取余来调整回原来的下标,例如规定大小为12个,但是指针指到了13,这时采用取余可以将13规整到1,这样就达到了环形的效果。
一.判断队列是否为空功能
def is_empty(self):
return self.rear == self.front
非常简单的实现,只需要看两个指针是否重叠即可!
二.判断队列是否为满功能
def is_full(self):
return (self.rear+1)%self.size == self.front
这个功能就与队列是否为空有些许不同,由于判满是判断rear指针向后移动一位是否与front重合,但有一种可能是rear再加一就会越界,因此对于变化的指针都需要进行取余操作。
三.增加元素功能
def push(self,element): # element为待插入元素
if not self.is_full(): # 若不为满可以插入
self.rear = (self.rear+1)%self.size
self.queue[self.rear] = element
self.new_capacity() # 完善后的插入功能,详情见后文
else: # 已经满了
print("队列已经满了无法插入")
raise IndexError
由于队列存在大小的规定,因此对插入也要分外注意,即存在队列已经满了的情况时无法插入的,所以分情况讨论即可,注意rear和front变化时,都需要进行取余操作!
四.删除元素功能
def pop(self):
if not self.is_empty(): # 若队列不为空可以删除
self.front = (self.front+1)%self.size
return self.queue[self.front] # 返回已删除的元素
else: # 队列为空
print("队列已经没有元素可以删除了")
raise IndexError("queue is empty")
同样的判断是否为空即可,删除元素并不需要pop掉对应位置的值,只需指针移动即可,因为后续的添加会覆盖掉这个删除的值。
五.遍历队列内容功能
def print_queue(self):
for i in range((self.front+1)%self.size,(self.rear+1)%self.size):
print(self.queue[i],end=',')
此功能用于查看队列内的元素,从front+1到rear下标都是队内元素,注意环形队列只能存放size-1个元素。
六.增容功能(只需存在于插入功能后面,已在上文完善)
def new_capacity(self):
if self.is_full():
new_size = 4 if self.size == 0 else self.size*2
add_part = new_size - self.size
self.size = new_size
self.queue.extend([0 for _ in range(add_part)])
此功能附加在插入元素上,插入后会导致队满,因此每一次插入后判断一下是否需要增容即可。
至此,所有关于自设环形队列的功能就已经结束了,接下来进入库中的自带的队列介绍。
python自带队列
不同于作者自己设计的单向列表,库中自带的队列是一个双向队列,如上图所示,即可以轻松地在队头队尾,入出元素,详情如下图代码:
from collections import deque
queue = deque([2,6,5,8,9],7) # 第一个参数为队列中的元素,第二个参数为队列的最大长度
queue.append(0) # 队尾入元素
queue.appendleft(5) # 队头入元素
queue.pop() # 队尾出元素
queue.popleft() # 队头出元素
刷题时间:
①用队列实现栈
本题目较为简单,即利用两个队列来实现一个模拟栈,我们知道栈和队列最大的区别就是出元素和进元素的不同,因此我们需要借助两个队列的先进先出来实现栈的后进先出,怎么做呢?
我们定义queue1和queue2两个队列,若queue1为空,我们就直接向queue1中入元素,若queue1不为空,那么我们将queue1的元素从尾巴开始依次向queue2中入,直到queue1为空,再将新的元素入到queue1中,再将queue2中的元素从尾巴开始依次向queue1中入,这样删除只需要删除第一个元素就可以做到后进先出,总体思想也就是利用两个队列来倒序插入元素从而达到效果。
class MyStack:
def __init__(self):
self.queue_1 = []
self.queue_2 = []
def push(self, x: int) -> None:
if not self.queue_1: # queue_01为空
self.queue_1.append(x)
else:
length = len(self.queue_1)
for i in range(0,length):
self.queue_2.append(self.queue_1.pop(-1))
self.queue_1.append(x)
for i in range(0,length):
self.queue_1.append(self.queue_2.pop(-1))
return None
def pop(self) -> int:
pop_element = self.queue_1.pop(0)
return pop_element
def top(self) -> int:
return self.queue_1[0]
还有一道用栈实现队列的题目,思想与这题相仿,大家可以去练练手,找到两者之间的关系。
https://leetcode.cn/problems/implement-queue-using-stacks/
②设计前中后队列
这题十分简单读者可以自己尝试解答。
我采用的是最简单移动的移动法,其他简单方法读者可以自己尝试。
class FrontMiddleBackQueue:
def __init__(self):
self.rear = 0
self.front = 0
self.queue = []
def pushFront(self, val: int) -> None:
i = len(self.queue)-1
self.queue.append(0)
while i>=0:
self.queue[i+1] = self.queue[i]
i-=1
self.queue[0] = val
return None
def pushMiddle(self, val: int) -> None:
mid = (len(self.queue))//2 # 待插入位置的下标
i = len(self.queue)-1
self.queue.append(0)
while i>=mid:
self.queue[i+1] = self.queue[i]
i-=1
self.queue[mid] = val
return None
def pushBack(self, val: int) -> None:
self.queue.append(val)
return None
def popFront(self) -> int:
if len(self.queue) == 0:
return -1
return self.queue.pop(0)
def popMiddle(self) -> int:
length = len(self.queue)
if length == 0:
return -1
if length%2 == 0:
mid = (length)//2-1
else:
mid = (length)//2
return self.queue.pop(mid)
def popBack(self) -> int:
if len(self.queue) == 0:
return -1
return self.queue.pop(-1)
总结:队列作为简单地线性结构十分简单,但应用却不少,如读取文件的最后几行内容等等,希望读者可以自己开发其一些作用,下一篇文章我也会加入一个迷宫问题用栈和队列去解决,希望大家喜欢~
若想复习栈的相关知识,可以参考我的上一篇文章:
https://blog.csdn.net/wangzhennin/article/details/136173503?spm=1001.2014.3001.5501