队列
- 一、队列介绍
- 二、队列分类及实现
- 1.线性队列(普通)
- 2.线性队列(双指针)
- 3.循环队列(双指针)
- 三、队列总结
- `线性队列(非循环队列)`:
- **使用双指针**:
- 不使用双指针(仅使用 pop(0) 和 append):
- `循环队列:`
- 总结:
提示:以下是本篇文章正文内容,下面案例可供参考
一、队列介绍
二、队列分类及实现
1.线性队列(普通)
tips:
直接采用pop弹出的方式读取元素实际会造成后续所有元素的整体向前移动,这与双指针方式不同,因此不会造成空间浪费。但是前移会造成时间复杂度升高到O(n),而采用双指针则是O(1)。但是这种方式简单容易实现,简单场景下比较适合。
class Queue():
def __init__(self):
self.queue=[]
def enqueue(self,element):
self.queue.append(element)
def dequeue(self):
if len(self.queue)>0:
return self.queue.pop(0)
else:
return None
def is_empty(self):
return len(self.queue)==0
if __name__=='__main__':
queue=Queue()
queue.enqueue(1)
queue.enqueue(6)
queue.enqueue(9)
print(queue.dequeue())
print(queue.dequeue())
print(queue.dequeue())
print(queue.dequeue())
2.线性队列(双指针)
tips:
本身采用双指针就会造成读取过元素的位置的空间浪费,因为rear不会逆序移动,因此此处仅供对比线性和循环的区别,下面代码可能没有实际用处,真正需要采用双指针应该采用循环队列,这样可以回收内存,以下仅供参考(可能有bug)
class Queue:
def __init__(self,size=3):
self.size=size
self.queue=[None for i in range(self.size)]
self.rear=0#队尾指针
self.front=0#队首指针
def enqueue(self,element):
if self.is_full():
raise MemoryError('Queue is full')
else:
self.rear+=1
self.queue[self.rear]=element
def dequeue(self):
if self.is_empty():
raise MemoryError('Queue is empty')
else:
self.front+=1
result=self.queue[self.front]
self.queue[self.front]=None#此处不处理也可以,后面会覆盖
return result
def is_full(self):
return self.rear==self.size-1
def is_empty(self):
return self.front==self.rear
if __name__=='__main__':
queue=Queue(5)
queue.enqueue(1)
print(queue.dequeue())
queue.enqueue(6)
print(queue.dequeue())
queue.enqueue(9)
queue.enqueue(2)
print(queue.dequeue())
print(queue.dequeue())
3.循环队列(双指针)
tips:
相比较于线性pop方式的高时间复杂度,以及双指针的线性方式的空间浪费,循环队列几乎避免了所有·缺点。它不会造成空间浪费,时间复杂度是O(1)。
注意事项
:以下rear和front是先移动,再存储/读取元素;其实先存/读,后移动也可以,但是有一个问题,尽量size大一点,比如size=10,实际存9就极限了,10会报错,因为两个指针会重合
class Queue:
def __init__(self,size=3):
self.size=size
self.queue=[0 for i in range(self.size)]
self.rear=0#队尾指针
self.front=0#队首指针
def enqueue(self,element):
if self.is_full():
raise MemoryError('Queue is full')
else:
self.rear=(self.rear+1)%self.size
self.queue[self.rear]=element
def dequeue(self):
if self.is_empty():
raise MemoryError('Queue is empty')
else:
self.front=(self.front+1)%self.size
result=self.queue[self.front]
self.queue[self.front]=None#此处不处理也可以,后面会覆盖
return result
def is_full(self):
return (self.rear+1)%self.size==self.front
def is_empty(self):
return self.front==self.rear
if __name__=='__main__':
queue=Queue(5)
queue.enqueue(1)
print(queue.dequeue())
queue.enqueue(6)
print(queue.dequeue())
queue.enqueue(9)
queue.enqueue(2)
print(queue.dequeue())
print(queue.dequeue())
三、队列总结
线性队列(非循环队列)
:
使用双指针:
-
优点:
- 入队和出队操作的时间复杂度都是 O(1)。
- 直观地表示了队列的头部和尾部。
-
缺点:
- 如果频繁进行出队操作,会导致队列前面出现空位,这些空位不能被用来存储新的入队元素,直到
rear
指针也移动到这些位置。 - 存在存储空间的浪费,因为队列的前端可能有很多未使用的空间,而
rear
指针只能在队列的末尾添加新元素。
- 如果频繁进行出队操作,会导致队列前面出现空位,这些空位不能被用来存储新的入队元素,直到
不使用双指针(仅使用 pop(0) 和 append):
-
优点:
- 空间不会被浪费,因为
append
操作总是在列表的末尾添加元素,而pop(0)
操作总是在列表的开头移除元素,所以不会有未使用的空间。 - 代码实现简单,直接使用列表的
pop(0)
和append
方法。
- 空间不会被浪费,因为
-
缺点:
- 出队操作的时间复杂度是 O(n),因为
pop(0)
需要将所有其他元素向前移动一位。
- 出队操作的时间复杂度是 O(n),因为
循环队列:
-
优点:
- 通过循环利用数组空间,避免了非循环队列中的空间浪费问题。
- 入队和出队操作的时间复杂度都是 O(1)。
-
缺点:
- 实现相对复杂,需要额外的逻辑来处理
front
和rear
指针的循环。 - 需要处理队列满和队列空的条件,这可能涉及到取模运算。
- 有大小限制,不能无限添加元素。
- 实现相对复杂,需要额外的逻辑来处理
总结:
- 线性队列(非循环)在使用双指针时可能会有存储空间的浪费,因为
rear
指针不能前移来利用前面空出来的位置。不使用双指针时,虽然空间不会被浪费,但出队操作的时间复杂度较高。 - 循环队列通过允许
rear
指针在到达数组末尾时回到数组开头,解决了空间浪费的问题,使得队列可以更有效地利用数组空间,并且保持了入队和出队操作的高效率。
在选择队列的实现方式时,需要根据具体的应用场景和性能要求来决定使用哪种方法。如果性能是关键因素,特别是在队列操作频繁的场景下,循环队列可能是更好的选择。如果队列操作较少,或者对实现的复杂性有要求,可能就会选择更简单的线性队列实现方式。
声明:
本文为本人的学习笔记,旨在记录和分享个人在学习过程中的心得体会和原创代码。由于本人刚入门,对相关知识的理解可能还存在不足之处,文章中难免会有错误或不准确的地方。在此,我诚挚地欢迎各位读者在阅读过程中,如果发现任何问题或有其他建议,随时在评论区或通过其他方式与我交流。我将虚心听取大家的意见,及时修正和改进文章内容,以便更好地学习和成长。感谢大家的关注和支持!