7.双向链表为底层的Queue队列数据结构的实现

(一)对双向链表的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)# 初始化将要操作的node对象
        if not self.end:            
            self.root.next=node# 挂载在root后面

            node.prev=self.root#检查node自身的prev和next是否更新

            self.end=node# 这是链表为空的情形,需要更新root和end
     
        else:
            self.end.next=node# 挂载在end后面
            
            node.prev=self.end# 检查node自身的prev和next是否更新

            self.end=node# 这是链表不为空的情形,只需要更新end
        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# 这里不能忘记,将之前的第一个节点的prev设置上
            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 # 不在while中时候也能输出最后一个节点的值
        
    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 # 不在while中也能输出root.next的值
由于Queue的ADT又需要删除队首元素,在链表里新写了remove_first方法
    def remove_first(self):
        if not self.root.next:
            raise IndexError("remove from empty list")  # 如果整个链表此时为空,返回index异常,给出空列表提示
        node_to_remove=self.root.next
        if node_to_remove.next: 
            self.root.next=node_to_remove.next  # 如果要删除的节点它的下一个节点存在,就进行常规的删除操作
            return node_to_remove.value  # return语句要放在流程最后,如果向上移动两行,则正经的删除操作会被跳过而不会执行
        else:
            self.root.next=None     # 如果删除的节点就是唯一的元素,删除操作就相当于将root.next和end置空
            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)的效率,这符合队列的基本操作方面的需求,
		队列的操作并不要求对中间内容的操作具有很高的处理速度。
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值