双向链表的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
是有用的域,很容易出错。