(一)对双向链表的Queue的ADT进行设计
Queue主要应该有的内容
一个Queue主要应该有的内容:
enqueue:在队尾插入数据---这里可以使用链表的append直接加在后面
dequeue:在队首删除数据---这里要特地在链表里新写一个remove_first方法
is_empty:判断队列是否为空---这里要利用链表的哨兵节点
Queue的双向链表的 ADT设计
class LinkedQueueADT(ABC):
@abstractclassmethod
def __init__(self,size):
'''
这里采用链表实现Queue抽象类型的容器,作为Queue存储和操作数据的基础,
这样设计的Queue容器就会具有在首尾的插入具有O(1)高效的同时还具备在中间遍历元素时候的灵活性和适中的效率
size参数用来规定链表的最大限度,再设置length用于知晓内部的实际数据长度
'''
pass
@abstractclassmethod
def enqueue(self,value):
'''在队尾放数据'''
pass
@abstractclassmethod
def dequeue(self):
'''取数据返回,并删除数据(删除数据则需要在双链表里面再写一个方法来实现它。)'''
return
@abstractclassmethod
def is_empty(self):
'''判断容器内是否有数据,如果没有则返回false'''
return
(二)借用了之前双向链表的设计部分:
from abc import ABC,abstractclassmethod
class Node():
def __init__(self,value=None,prev=None,next=None):
'''init方法应该能创建一个供双链表使用的node节点,它应该具有前驱prev和后继的next的各自的指针,以及自身的value三种属性。'''
self.value=value
self.prev=prev
self.next=next
class DoubleLinkedList():
def __init__(self):
self.root=Node()
self.size=0
self.end=None
def append(self,value):
'''
self.end是用来更新链表状态,self.root/end.next是用来挂载节点.
主要的操作步骤是:
1.挂载节点
2.更新node的前驱后继状态。
3.更新链表自身的首尾状态。
'''
node=Node(value)
if not self.end:
self.root.next=node
node.prev=self.root
self.end=node
else:
self.end.next=node
node.prev=self.end
self.end=node
self.size+=1
def apppend_first(self,value):
node=Node(value)
if not self.end:
self.root.next=node
node.prev=self.root
self.end=node
else:
node.next=self.root.next
self.root.next.prev=node
self.root.next=node
node.prev=self.root
self.size+=1
def __iter__(self):
current=self.root.next
if current:
while current is not self.end:
yield current.value
'''yield和return类似,yield使用于循环中,返回一次值但不会终止该函数'''
current=current.next
yield current.value
def reverse_iter(self):
'''双链表不同于单链表,双链表可以回头,逆向遍历'''
current=self.end
if current:
while current is not self.root.next:
yield current.value
current=current.prev
yield current.value
由于Queue的ADT又需要删除队首元素,在链表里新写了remove_first方法
def remove_first(self):
if not self.root.next:
raise IndexError("remove from empty list")
node_to_remove=self.root.next
if node_to_remove.next:
self.root.next=node_to_remove.next
return node_to_remove.value
else:
self.root.next=None
self.end=None
return node_to_remove.value
(三)双向链表的Queue在前面设计思想上的实现
class Queue(LinkedQueueADT):
def __init__(self,size=4):
'''init方法用于产生Queue容器的实例对象,需要传进一个参数size用于创建最大队列,具体的容器的实现它的数据结构是DoubleLinkedList双向链表'''
self.item=DoubleLinkedList()
self.size=size
self.length=0
def enqueue(self,value):
'''利用实例对象,调用它的item对象存储的DoubleLinkedList实例对象'''
self.item.append(value)
self.length+=1
def dequeue(self):
return self.item.remove_first()
self.length-=1
def is_empty(self):
if not self.item.root.next:
return "is empty"
else:
return "is not empty"
def __iter__(self):
self.item.__iter__()
if __name__=='__main__':
queue=Queue(4)
queue.enqueue("一")
queue.enqueue("二")
queue.enqueue("三")
queue.enqueue("四")
print(queue.is_empty())
print(queue.dequeue())
print(queue.dequeue())
print(queue.dequeue())
print(queue.dequeue())
(四)分析双向链表的队列优劣势:
根本上而言,队列容器还是优劣势体现在对应实现它的数据结构:
我的理解大致认为:
链表更注重内存方面'量的变化',而保证基本运行,不追求内部的极致效率。
它很适合动态变化,陡然增长又陡然减少的数据量,尽量保证运行效率和基础的操作。
而数组是内存方面,数据的数量尽量不要变化太多,对存放的常用数据保证高速的执行效率。
双向链表的队列
1.具有临时扩展性,可以灵活地在已有的链表长度后继续增加节点,不需要
一开始就分配固定的内存大小。
(即使设置了链表的size也是代码逻辑中暂时不允许,我们要讨论的物理存储机制层面)
链表的扩展性是物理机制上不同于数组的存在,链表的扩容十分方便,
只需在存储空间内找到一处空余的碎片化内存,进行非连续性,
单个元素独立计算的内存空间,从来不需要大片预留连续内存空间,
只需分配新节点,更新指针即可,效率是O(1).
而数组则需要从10扩展到20时候,只能成倍地扩容,而且数组的扩容复杂,
过程中需要在存储空间找到一大片连续的内存资源,涉及到内存分配和元素复制,
是代价很高的操作,效率是O(n).
2.内存的利用率高,很适合不确定内存大小,或者内存需求大小变化很大的场景。
比如流数据,传感器,日志,用户请求,都是在某时间突然增多且不定量的数据。
3.首尾处的插入删除操作具有链表自身O(1)的效率,这符合队列的基本操作方面的需求,
队列的操作并不要求对中间内容的操作具有很高的处理速度。