双向链表的python实现&双向链表实现双端队列

双向链表的python实现

定义

  • 链表的每个节点都维护了指向其前驱节点和后继节点的引用,称之为双向链表。

头哨兵和尾哨兵的概念及优点

  • 为了避免在操作靠近链表边界节点时出现特殊情况,我们在双向链表的首尾两端维护了两个特殊的节点,即头哨兵和尾哨兵,头哨兵的next域存储指向下一节点的引用,尾节点的prev(前指针域)存储指向其前驱节点的引用。

    当使用哨兵节点时,一个链表需要初始化,使头节点的next指向尾节点,尾节点的prev指向头节点,哨兵节点的其他域(头节点的prev和元素域,尾节点的next和元素域)是无关紧要的。对于一个非空的双向链表,头节点的next将指向序列中第一个真正包含元素的节点,尾节点的prev将指向序列对后一个真正包含元素的节点。

  • 使用哨兵节点的优点在于,可以使用较少地额外空间就简化了操作的逻辑。最明显的是,头和尾节点从不曾改变,改变的是头节点和尾节点之间的其他节点,所有插入节点的操作都是一样的,即新节点放在一对已知节点之间,而删除节点的操作,都是确保被删除的节点前后处于一对节点之间。

  • 对于单链表实现的队列来说,在enqueue(),由于节点是在列表的尾部加上去的,对于空列表,需要单独判断,并将self._head的引用至新节点,而如果使用双向链表来实现,则对空列的头节点(这里是指列表第一个有元素的节点)的操作和其他节点的操作都是一样的,因为列表已经初始化了一对节点,即头哨兵和尾哨兵节点。

    def enqueue(self, value):
            new_node = self.Node(None, value)
            if self.is_empty():
                self._head = new_node
            else:
                self._tail.element = new_node
            self._tail = new_node
            self._size += 1
    
  • 由于使用了头尾哨兵,那么对序列第一个元素和最后一个元素的的删除都可以使用相同的方法来实现。

双向链表的基本实现

  • 首先定义一个基本的双向链表类,DoubleLinkBase(),在这个类中再定义一个节点类Node(),通过节点类实例化的每一个节点应当拥有三个基本属性,即元素elemect,前引用prev,后引用next。

  • 在DoubleLinkBase()类里面,定义了一些双向链表的基本方法。insert_between()和delete_node()方法提供了向链表插入和删除节点的基本逻辑,但是需要一个或多个节点的引用作为参数(理解为以节点作为参数)。insert_node()方法返回的是对新创建的节点的引用(理解为返回的是新创建的节点new_node);delete_node()方法基本逻辑是使被删除节点的相邻节点相链接,使列表绕过被删除的节点,增加target_node._prev = target_node._next = target_node._element = None操作的目的是被删除的节点以及其与其他节点不必要的链接和元素会被消除,有利于python进行垃圾回收。

  • class DoubleLinkBase(object):
        class Node(object):
            """
            节点类
            """
            __slots__ = "_element", "_prev", "_next"
    
            def __init__(self, element, next, prev):
                self._next = next
                self._prev = prev
                self._element = element
    
        def __init__(self):
            """
            初始化链表实例,链表实例有三个基本属性
            """
            self.header = self.Node(None, None, None)
            self.tail = self.Node(None, None, None)
            self.size = 0
            self.header._next = self.tail
            self.tail._prev = self.header
    
        def __len__(self):
            return self.size
    
        def is_empty(self):
            return self.size == 0
    
        def insert_between(self, value, prev_node, next_node):
            new_node = self.Node(value, None, None)
            prev_node._next = new_node
            next_node._prev = new_node
            self.size += 1
            return new_node
    
        def delete_node(self, target_node):
            prev_node = target_node._prev
            next_node = target_node._next
            element = target_node._element
            prev_node._next = next_node
            next_node._prev = prev_node
            self.size -= 1
            target_node._prev = target_node._next = target_node._element = None
            return element
    

用双向链表实现双端队列

  • 继承上文的双向链表类DoubleLinkBase()类实现双端队列类DoubleLinkQueue()类,并以基类的方法实现双端队列的一些特性。

  • DoubleLinkQueue()需要实现的方法:

    • first : 返回队列前端的第一个元素;
    • last : 返回队列后端的最后一个元素;
    • add_first : 在队列前端添加一个元素;
    • add_last : 在队列后端添加一个元素;
    • delete_first : 在队列前端删除一个元素并返回元素值;
    • delete_last : 在队列后端删除一个元素并返回元素值;
    • len : 返回队列中元素的个数(基类以实现)。
  • 实现双端队列的关键在于:记住双端队列的第一个元素并不存储在头节点(头哨兵),而是在头节点(头哨兵)之后的第一个节点;队列的最后一个元素也不在尾节点(尾哨兵),而是在尾节点(尾哨兵)之前的第一个节点。

  • # !/usr/bin/env python
    # -*-coding:utf-8 -*-
    
    """
    # File       : DoubleLink_of_python.py
    # Time       :2022/1/15 9:58
    # Author     :kkk
    # version    :python 3.6
    # Description:本文件用于解释如何构建双向链表
    """
    
    
    class DoubleLinkBase(object):
        class Node(object):
            """
            节点类
            """
            __slots__ = "_element", "_prev", "_next"
    
            def __init__(self, element, next, prev):
                self._next = next
                self._prev = prev
                self._element = element
    
        def __init__(self):
            """
            初始化链表实例,链表实例有三个基本属性
            """
            self.header = self.Node(None, None, None)
            self.tail = self.Node(None, None, None)
            self.size = 0
            self.header._next = self.tail
            self.tail._prev = self.header
    
        def __len__(self):
            return self.size
    
        def is_empty(self):
            return self.size == 0
    
        def insert_between(self, value, prev_node, next_node):
            new_node = self.Node(value, None, None)
            prev_node._next = new_node
            next_node._prev = new_node
            self.size += 1
            return new_node
    
        def delete_node(self, target_node):
            prev_node = target_node._prev
            next_node = target_node._next
            element = target_node._element
            prev_node._next = next_node
            next_node._prev = prev_node
            self.size -= 1
            target_node._prev = target_node._next = target_node._element = None
            return element
    
    
    class DoubleLinkQueue(DoubleLinkBase):
    
        def first(self):
            if self.is_empty():
                return "the queue is empty"
            return self.header._next._element
    
        def last(self):
            if self.is_empty():
                return "the queue is empty"
            return self.tail._prev._element
    
        def insert_first(self, value):
            prev_node = self.header
            next_node = self.header._next
            self.insert_between(value, prev_node, next_node)
    
        def insert_last(self, value):
            prev_node = self.tail._prev
            next_node = self.tail
            self.insert_between(value, prev_node, next_node)
    
        def delete_first(self):
            if self.is_empty():
                return "the queue is empty"
            self.delete_node(self.header._next)
    
        def delete_last(self):
            if self.is_empty():
                return "the queue is empty"
            self.delete_node(self.tail._prev)
    
    
    if __name__ == "__main__":
        Q = DoubleLinkQueue()
        Q.insert_first(1)
        Q.insert_last(2)
        print(len(Q))
        print(Q.first())
        print(Q.last())
    

    在写代码的过程中需要注意头哨兵是只有_next,尾哨兵只有_prev是有用的域,很容易出错。

/* * 基于双向链表实现双端队列结构 */ package dsa; public class Deque_DLNode implements Deque { protected DLNode header;//指向头节点(哨兵) protected DLNode trailer;//指向尾节点(哨兵) protected int size;//队列中元素的数目 //构造函数 public Deque_DLNode() { header = new DLNode(); trailer = new DLNode(); header.setNext(trailer); trailer.setPrev(header); size = 0; } //返回队列中元素数目 public int getSize() { return size; } //判断队列是否为空 public boolean isEmpty() { return (0 == size) ? true : false; } //取首元素(但不删除) public Object first() throws ExceptionQueueEmpty { if (isEmpty()) throw new ExceptionQueueEmpty("意外:双端队列为空"); return header.getNext().getElem(); } //取末元素(但不删除) public Object last() throws ExceptionQueueEmpty { if (isEmpty()) throw new ExceptionQueueEmpty("意外:双端队列为空"); return trailer.getPrev().getElem(); } //在队列前端插入新节点 public void insertFirst(Object obj) { DLNode second = header.getNext(); DLNode first = new DLNode(obj, header, second); second.setPrev(first); header.setNext(first); size++; } //在队列后端插入新节点 public void insertLast(Object obj) { DLNode second = trailer.getPrev(); DLNode first = new DLNode(obj, second, trailer); second.setNext(first); trailer.setPrev(first); size++; } //删除首节点 public Object removeFirst() throws ExceptionQueueEmpty { if (isEmpty()) throw new ExceptionQueueEmpty("意外:双端队列为空"); DLNode first = header.getNext(); DLNode second = first.getNext(); Object obj = first.getElem(); header.setNext(second); second.setPrev(header); size--; return(obj); } //删除末节点 public Object removeLast() throws ExceptionQueueEmpty { if (isEmpty()) throw new ExceptionQueueEmpty("意外:双端队列为空"); DLNode first = trailer.getPrev(); DLNode second = first.getPrev(); Object obj = first.getElem(); trailer.setPrev(second); second.setNext(trailer); size--; return(obj); } //遍历 public void Traversal() { DLNode p = header.getNext(); while (p != trailer) { System.out.print(p.getElem()+" "); p = p.getNex
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值