python语言实现双向链表
双向链表顾名思义,存在两个指针:头指针和尾指针,分别指向前驱节点和后续节点。
双向链表所包含的操作如下:
- is_empty() 链表为空
- length() 链表长度
- trvael() 便利链表
- add(item) 头部添加元素
- append(item) 尾部添加元素
- insert(pos, item) 指定位置添加元素
- remove(item) 删除节点
- search(item) 查找元素
相对于单向链表的区别:
- 1.多了头指针,指向前驱节点
- 2.添加、删除节点与单链表不同,判空、长度、遍历、查找都相同,具体代码请参照此链接。
- 3.删除节点操作需要考虑多种情况,删除头结点或尾节点需判断后继节点是否为空。
添加节点
头部添加节点
头部添加节点步骤如下:
- 1.将新节点尾指针指向原头结点
- 2.不为空链表时,原有头结点头指针指向新节点
- 3.头结点变更为新节点
代码如下:
def add(self, item):
'''头部添加元素'''
node = Node(item)
# 先让新节点指向头结点后部分的链表,保证后面不丢失
node.next = self._head
# 考虑特殊情况空链表,此时头结点为None,没有前指针,指向node会报错
if self._head is not None:
self._head.pre = node
# 再更换头结点
self._head = node
头插注意点有两处:
- 1.考虑链表为空
此时头结点为空,没有头指针、尾指针,所以为空时以下代码会报错:
self._head.pre = node
采用的解决方案是利用头节点的头指针时先判断头结点是否为空(若为空,则表明是空链表),不为空在利用头结点的头指针,为空添加的节点就是头结点,无需再为其头指针分配指向。
- 2.添加节点时需分配后继节点的前指针
尾部插入节点
尾部插入节点步骤如下:
- 1.判断是否为空节点,若为空节点直接添加作为头结点。
- 2.若不为空节点,遍历寻找尾节点,尾节点尾指针指向新节点,
- 3.新节点头指针指向原尾节点。
代码如下:
def append(self, item):
'''尾部添加元素'''
node = Node(item)
# 判断是否为空链表,为空直接添加作为头结点
if self.is_empty():
self._head = node
else:
cur = self._head
# 寻找尾节点
while cur.next != None:
cur = cur.next
# 尾节点尾指针指向新节点
cur.next = node
# 新节点头指针指向原尾节点
node.prev = cur
指定位置插入节点
指定位置插入节点步骤如下:
- 1.判断位置是否合理,小于等于1作为头插,大于链表长度作为尾插。
- 2.遍历找到指定位置索引,将节点插到位置索引之前。
- 3.建立连接,新节点尾指针指向原节点,原节点头指针指向新节点,这里原节点是指索引节点和索引节点的前驱节点。
新节点与原节点连接方式如下图:
代码如下:
def insert(self, pos, item):
'''指定位置添加'''
# 当pos=1时,也是头插
if pos <= 1:
self.add(item)
# 当pos大于等于尾节点索引时为尾插,索引从0开始,length-1即为尾部节点位置索引
elif pos >= self.length()-1:
self.append(item)
else:
node = Node(item)
cur = self._head
count = 0
# 寻找要插入的位置游标
while count < pos:
count += 1
cur = cur.next
# 新节点尾指针,指向索引节点
node.next = cur
# 新节点头指针,指向索引节点前驱节点
node.prev = cur.prev
# 索引节点头指针,指向新节点
cur.prev = node
# 索引节点前驱结点尾指针,指向新节点
cur.prev.next = node
注意,新节点与原有节点连接顺序并无强制要求,按照便于理解的方式来编写即可。
删除节点
双向链表节点变动都要考虑头指针和尾指针,当要删除的节点为头结点时,若此时链表只有一个节点,后继节点为None,不存在头指针,此时引用会报错;当要删除的节点为尾节点时,后继节点也为None,无需考虑后继节点前指针。所以这两种情况作为特殊情况单独考虑。
双向链表删除节点步骤如下:
- 1.遍历链表,当前节点值等于要删除的值时:判断是否为头结点,若是头结点再判断是否链表只包含单个节点,判断完毕进行节点删除。
- 2.非头结点时判断是否为尾节点,判断完毕进行节点删除。
- 3.删除节点操作为:
- 1.删除节点前驱节点的尾指针,指向删除节点的后驱节点。
- 2.删除节点后驱节点的头指针,指向删除节点的前驱节点。
删除节点的操作如下图:
代码如下:
def remove(self, item):
'''删除节点'''
cur = self._head
# 遍历链表
while(cur != None):
# 找到需删除的节点时
if cur.elem == item:
# 考虑头结点情况
if cur == self._head:
self._head = cur.next
# 若链表不止包含一个节点,及cur节点的后继节点不为None
if cur.next:
cur.next.prev = None
else:
cur.prev.next = cur.next
# 若不为尾节点,不为尾节点时cur的后继节点不为None
if cur.next:
cur.next.prev = cur.prev
break
# 没找到需删除的元素时,指针后移
else:
cur = cur.next
ALL IN
完整代码如下:
class Node(object):
"""定义节点"""
def __init__(self, item):
self.elem = item
self.next = None
self.prev = None
class DoubleLinkList(object):
"""双链表,添加、删除节点与单链表不同,判空、长度、遍历、查找都相同"""
def __init__(self, node=None):
self._head = node
def is_empty(self):
'''判断链表是否为空'''
return self._head is None
def length(self):
'''返回链表长度'''
# cur用来移动遍历数组
cur = self._head
# count记录数量
count = 0
while cur != None:
count += 1
cur = cur.next
return count
def trval(self):
'''遍历列表'''
# cur用来移动遍历数组
cur = self._head
while cur != None:
print(cur.elem, end=" ")
cur = cur.next
def add(self, item):
'''头部添加元素'''
node = Node(item)
# 先让新节点指向头结点后部分的链表,保证后面不丢失
node.next = self._head
# 考虑特殊情况空链表,此时头结点为None,没有前指针,指向node会报错
if self._head is not None:
self._head.pre = node
# 再更换头结点
self._head = node
#node.next.prev = node
def append(self, item):
'''尾部添加元素'''
node = Node(item)
# 判断是否为空链表,为空直接加到头结点后面
if self.is_empty():
self._head = node
else:
cur = self._head
# 找到尾结点,添加到尾结点后面
while cur.next != None:
cur = cur.next
cur.next = node
node.prev = cur
def insert(self, pos, item):
'''指定位置添加'''
# 当pos=1时,也是头插
if pos <= 1:
self.add(item)
elif pos >= self.length()-1:
self.append(item)
else:
node = Node(item)
cur = self._head
count = 0
# 寻找要插入的位置游标
while count < pos:
count += 1
cur = cur.next
cur.prev.next = node
cur.prev = node
node.next = cur
node.prev = cur.prev
def remove(self, item):
'''删除节点'''
#pre = None
cur = self._head
while(cur != None):
if cur.elem == item:
# 考虑头结点情况
if cur == self._head:
self._head = cur.next
# 判断链表是否只有一个节点
if cur.next:
cur.next.prev = None
else:
cur.prev.next = cur.next
# 尾结点单独考虑
if cur.next:
cur.next.prev = cur.prev
break
else:
#pre = cur
cur = cur.next
def search(self, item):
'''查找节点是否存在'''
cur = self._head
while(cur != None):
if cur.elem == item:
return true
else:
cur = cur.next
return False
if __name__ =="__main__":
ll = DoubleLinkList()
print(ll.is_empty())
ll.add(4)
ll.add(1)
ll.append(2)
ll.insert(1,3)
ll.add(4)
ll.add(5)
ll.insert(5,333)
ll.trval()
对于双链表,需注意节点变动要同时考虑头指针和尾指针,还要考虑是否存在后继节点,不存在后继节点则相关指针也不存在,节点变动时不予考虑。