1 定义
在之前的博客:数据结构与算法 | 链表-2:单链表的实现中笔者提到了单向链表的相关实现,本期博客将注重单向循环链表的实现!
单向循环链表其实就是在单向链表的基础之上做了一点改进,就是尾节点的next区域并不是指向None了,而是指向头结点!
2 Python手写单向循环链表
2.1 先定义节点
class Node(object):
"""节点"""
def __init__(self, elem):
self.elem = elem
self.next = None
2.2 增删改查
class SinCycLinkedlist(object):
'''单向循环链表'''
def __init__(self, node=None): # 设置一个默认参数
self.__head = node # 双下划线表示私有属性 不让别人看到 仅自己用
# node的next指向自身 如果传入了非空节点
if node:
node.next = node
def is_empty(self):
'''is_empty() 链表是否为空'''
return self.__head == None
def length(self):
'''length() 链表长度'''
# 另外下面对于空链表的特殊情况符合,返回0
# 先定义辅助的cur cur游标,用来移动遍历节点
if self.is_empty():
# 针对空链表
return 0
else:
cur = self.__head
# count用来计数
count = 1
# while cur != None: # 区别于cur.next
# 但是尾部节点不是None了 而是指向头部节点 所以要变化
while cur.next != self.__head:
count += 1
cur = cur.next
return count
# 1个节点的链表也可以cover
def travel(self):
'''travel() 遍历整个链表'''
if self.is_empty():
# 先看是否为空链表
pass
else:
cur = self.__head # 进入了第一个结点
while cur.next != self.__head:
# 保证每一个结点的元素都打印出来即可
# 但现在最后一个节点没打印出来!
print(cur.elem, end = ' ') # 打印元素值
cur = cur.next
# 所以还要再加一个
print(cur.elem, end = ' ')
print(' ')
# 一开始是空链表呢?所以没有next 故重新考虑加条件
# 1个节点没问题
def add(self, item):
'''链表头部添加元素,头插法
时间复杂度:O(1)
'''
# 先将加入的元素指向原有首节点,然后让头结点指向新插入的元素
node = Node(item)
# 这时候先改变谁的指向呢?最后一个还是首部的?
# 先改最后一个
if self.is_empty():
# 如果为空链表的情况
self.__head = node
node.next = node # 指向自身
else:
cur = self.__head
while cur.next != self.__head:
cur = cur.next
# 记录了游标位置
# 再改头部
node.next = self.__head
self.__head = node
# 修改尾节点指向
cur.next = node
# 能处理原有链表为空链表的情况
def append(self, item):
'''链表尾部添加元素,尾插法
时间复杂度:O(n) 循环找到尾部
'''
# 先定义一个节点 然后循环到最后一个
node = Node(item)
if self.is_empty():
# 如果为空 直接头结点指向该插入的元素
self.__head = node
node.next = node
else:
# 非空引入一个游标cur
cur = self.__head
while cur.next != self.__head: # 区别于上面,因为要在最后一个节点指向新的!
# 这里无法直接兼容空链表 因为空链表没有.next
cur = cur.next
cur.next = node
node.next = self.__head
def insert(self, pos, item):
'''指定位置添加元素
参数pos:表示下标,从0开始索引
区别于之前的cur 用pre 其实意思一致
时间复杂度:O(n) 尾部插入 循环 也是n
'''
if pos <= 0:
# 认为是头插法
self.add(item)
elif pos > (self.length()-1):
# 认为是尾插法
self.append(item)
else:
# 中间插入和首尾的无关 所以中间不变!
pre = self.__head
count = 0
while count < (pos-1):
count += 1
pre = pre.next
# 当循环退出后,pre指向pos-1位置
node = Node(item)
node.next = pre.next # 先改变新节点 不打乱原来的关系
pre.next = node # 即pre.next指向node所在位置
def remove(self, item):
'''删除节点
引入两个游标 比较好理解
先cur指向首节点,pre指向None
然后让pre指向cur cur再移动
'''
if self.is_empty():
pass
else:
# 初始化
cur = self.__head
pre = None
# 再引入一个指针 一开始先找到尾部!
# 但是这样会比较浪费!是否有更优的方法?
# 放到循环里面!
# last = self.__head
# while last.next != self.__head:
# last = last.next
# 此时last就在尾节点
# 终止条件判断
while cur.next != self.__head:
if cur.elem == item:
# 先判断此节点是否是头节点
if cur == self.__head:
# 头结点-需要先找到尾节点的游标last
last = self.__head
while last.next != self.__head:
last = last.next
# 此时last就在尾节点
self.__head = cur.next
# 让尾节点指向cur.next
last.next = self.__head
else:
# 中间节点-对应单链表的过程!
pre.next = cur.next
# 链表已经完成一条主线 不用管这个值对应下面的指向了
return
else:
# 开始移动 两个游标之间差1个位置
pre = cur
cur = cur.next
# 尾节点:如果找到的元素是尾节点
# 先判断
if cur.elem == item:
# 如果为单节点 pre为None 不移动 没有next
# if pre == None:
if cur == self.__head:
# 单节点
self.__head = None
else:
# 多节点
pre.next = self.__head
# 等价于 pre.next = cur.next
# 空链表不执行任何操作!可以没问题
# 如果删除首节点 需要变化的是头结点指向下一个!
# 如果只有一个节点,咋办呢?没问题 指向None
# 上面是头部情况,尾部情况捏?没有问题,直接指向None
def search(self, item):
'''查找节点是否存在
时间复杂度:O(n) 循环!
'''
if self.is_empty():
return False
cur = self.__head # 用来遍历链表
while cur.next != self.__head:
if cur.elem == item:
return True
else:
cur = cur.next
# 退出循环的时候补充尾部节点
if cur.elem == item:
return True
else:
return False
# 能解决空链表的问题 如果为空 直接return false
2.3 实例
if __name__ == "__main__":
ll = SinCycLinkedlist()
ll.add(1)
ll.add(2)
ll.append(3)
ll.insert(2, 4)
ll.insert(4, 5)
ll.insert(0, 6)
print ("length:",ll.length())
ll.travel()
print (ll.search(3))
print (ll.search(7))
ll.remove(1)
print ("length:",ll.length())
ll.travel()
ll.remove(6)
ll.travel()
ll.remove(5)
ll.travel()
length: 6
6 2 1 4 3 5
True
False
length: 5
6 2 4 3 5
2 4 3 5
2 4 3