循环队列判断队空和队满_数据结构与算法——栈与队列(二)
(二) 队列
定义
和栈相似,队列也是一种特殊的线性表,跟数组的不同之处也体现在增删等操作上。队列的插入操作只能在队列的末尾进行,队列的删除操作只能在队头进行,队列是一种先进先出的线性表。用数组实现的队列叫链式队列,用链表实现的队列叫链式队列。
队列的基本操作
和栈相似,对于队列,插入数据只能在队尾进行,删除数据只能在队头进行,在队列中插入数据我们叫入队enqueue,删除队列中的数据我们叫出队dequeue。
同样的我们分别基于顺序队列和链式队列对他们的增加删除操作进行讨论。
链式队列
对于链式队列,在初始化的时候同样需要定义两个指针,front指针与rear指针指向队列的尾部。当队列为空的时候,front指针和rear指针都指向空链表的头部。在出队时,只需要将front指针指向当前节点的下一个节点即可。
链式队列的入队,只需要将队尾指针rear指向的节点的next指针指向要插入的新节点即可。
class Node():
'''链表结构的Node节点'''
def __init__(self, val, next = None):
'''Node节点的初始化方法.
参数:
val:存储的数据
next:下一个Node节点的引用地址
'''
self.val = val
self.next = next
class LinkQueue():
"""链式队列"""
def __init__(self):
"""链式队列初始化"""
self.head, self.tail = None, None
def enqueue(self, num):
"""入队
参数:
num: 入队的数值
"""
node = Node(num)
if self.tail:
self.tail.next = node
else:
self.head = node
self.tail = node
return True
def dequeue(self):
"""出队
返回:
队首的值
"""
if self.head:
num = self.head.val
self.head = self.head.next
if not self.head:
self.tail = None
return num
return None
def PrintAll(self):
"""打印队列
返回:
整个队列
"""
if not self.head:
return None
res = []
node = self.head
while node:
res.append(node.val)
node = node.next
return '->'.join('%s'%num for num in res)
顺序队列
对于顺序队列,在初始化的时候需要定义两个指针并且给出队列的大小,front指针指向队列头部,rear指针指向队列的尾部。当队列为空的时候,front指针与rear指针都指向数组下标为0的位置。在出队时,只需要将front指针向后移一位即可,因此顺序队列的出队时间复杂度为O(1)。
对于顺序队列的入队操作,需要考虑队列是否已经满了的情况,如果rear指针已经指向数组的最后一个下标位置,此时需要判断,front指针是否经过出队操作后往后移动了,如果front指针不指向数组下标为0的位置,那么将front到rear指针之间的数据都移动到数组开头,然后front指针重新指向数组下标为0的位置,rear指针指向rear-front的位置。如果front指针指向数组下标为0的位置且rear指针指向数组最后一个位置,那么队列已经满了,此时则需要考虑扩容的问题。如果入队时的队列未满,且rear指针指向的位置不是数组尾部,那么入队的时间复杂度为O(1);如果队列未满且rear指针指向数组尾部,还有队列已满需要扩容这两种情况,由于涉及到数据的搬移,时间复杂度为O(n)。
对于顺序队列的入队操作,像上面那种rear指针指向数组尾部但是数组前面是空的假溢出情况,通常有两种简单粗暴的解决方法:
(1)第一种是像上面那样的解决方法,消耗O(n)的时间复杂度进行数据搬移
(2)第二种是初始化队列的时候,申请足够大的内存空间确保数组不越界
class ArrayQueue():
"""顺序队列"""
def __init__(self, capacity):
"""顺序队列的初始化
参数:
capacity: 顺序队列的大小
"""
self.queue = []
self.capacity = capacity
self.head, self.tail = 0, 0
def enqueue(self, num):
"""入队1
参数:
num: 入队的数值
"""
if self.tail == self.capacity:
return False
self.queue.append(num)
self.tail += 1
return True
def NewEnqueue(self, num):
"""入队2:队首有多余空间且队满的情况下,进行数据搬移后再入队
参数:
num: 入队的数值
"""
if self.tail == self.capacity:
if self.head == 0:
return False
for i in range(self.head, self.tail):
self.queue[i - self.head] = self.queue[i]
self.tail -= self.head
self.head = 0
self.queue[self.tail] = num
self.tail += 1
return True
def dequeue(self):
"""出队
返回:
队首的值
"""
if not self.queue:
return False
num = self.queue[0]
self.head += 1
return num
def PrintAll(self):
"""打印队列
返回:
整个队列
"""
if not self.queue:
print(None)
return False
print(self.queue[self.head: self.tail])
return True
对于第一种方法,由于当rear==n-1的时候,会有数据搬移,这点会影响到队列的入队操作性能;而对于第二种方法,由于不断的出列,数组前面会空出很多不使用的内存空间,这些空间也不被释放,会造成大量的内存空间浪费。
那么数组的越界问题有没有什么好的解决方法呢?接下来要说的循环队列,就是对这种问题的一种解决方法。
循环队列
我们将顺序队列的首尾相连就形成了一个环,如下图所示,可以看到队列的大小为8,front=4,rear= 7,当我们向队列中插入元素时,数据插入rear指向的位置并且rear往后移一位,这里要注意的是rear指针已经指向下标为7的位置了,所以当我们在rear处插入一个元素后,rear指针接下来会指向下标为6的位置。
上面是队列未满时的情况,如果队列已满,假设队列的情况如下图所示。
我们可以看到rear指针指向的位置为空,也就是说我们的循环队列在使用的时候会浪费一个存储空间。
那么怎么样判断队满与队空的情况呢?首先是判断队空,跟普通的队列相同,当front==rear时,队列是空的;队满的判断条件与其他的队列不同,我们可以用两种方法来判断循环队列是否已满。
第一种:我们在初始化循环队列时,设置一个标志位flag和一个计数值count,最开始rear=front且flag=count=0,当我们插入一个数据时,判断count是否等于队列大小n-1,如果是的话那么已经队满无法插入数据且令flag=1,否则我们将数据插入到rear当前的位置并将rear移向下一位,count++;同样的删除数据时,head指向他的下一个位置并且count—。
第二种:通过前人的总结,队满的条件为(rear + 1) % n == front,直接用这个条件判断是否队满。
class CircularQueue():
"""用数组实现的循环队列"""
def __init__(self, capacity):
"""顺序队列的初始化
参数:
capacity: 顺序队列的大小
"""
self.queue = []
self.capacity = capacity + 1
self.head, self.tail = 0, 0
def enqueue(self, num):
"""入队1
参数:
num: 入队的数值
"""
if (self.tail + 1) % self.capacity == self.head:
return False
self.queue.append(num)
self.tail = (self.tail + 1) % self.capacity
return True
def dequeue(self):
"""出队
返回:
队首的值
"""
if self.head != self.tail:
num = self.queue[self.head]
self.head = (self.head + 1) % self.capacity
return num
def PrintAll(self):
"""打印队列
返回:
整个队列
"""
if self.tail >= self.head:
return self.queue[self.head : self.tail]
res = self.queue[self.head :] + self.queue[:self.tail - 1]
return res
应用
1、约瑟夫问题
2、线程并发问题