概述
- 循环链表是一种特殊的链表,它的最后一个节点的
next指针不是指向null,而是指向链表的头节点(对于单循环链表),或者尾节点的next指针指向头节点,同时头节点的prev指针指向尾节点(对于双循环链表)。 - 这种结构形成了一个闭合的环,使得从任何一个节点出发都能遍历整个链表。
- 资料:
https://pan.quark.cn/s/43d906ddfa1b
一、循环链表的结构特点
- 闭合环形结构:链表中没有明确的"末尾"节点,最后一个节点与头节点相连。
- 遍历无边界:从任意节点开始,沿着指针方向可以无限循环遍历所有节点(需要注意遍历的终止条件,否则会陷入死循环)。
- 动态大小:和普通链表一样,可以动态地添加或删除节点。
- 可从任意节点访问:由于是环形,访问链表元素不一定要从头节点开始。
循环链表主要分为两种:
- 单循环链表 (Circular Singly Linked List) :基于单链表,最后一个节点的
next指针指向头节点。 - 双循环链表 (Circular Doubly Linked List) :基于双链表,最后一个节点的
next指针指向头节点,同时头节点的prev指针指向最后一个节点。
二、单循环链表的基本操作及实现
我们重点讲解单循环链表。其节点结构与普通单链表相同。
python
class Node:
"""
定义单链表的节点
"""
def __init__(self, data):
self.data = data
self.next = None
class CircularSinglyLinkedList:
"""
定义单循环链表
"""
def __init__(self):
self.head = None
self.size = 0
def is_empty(self):
"""
判断链表是否为空
"""
return self.head is None
def length(self):
"""
获取链表的长度
时间复杂度: O(n)
"""
if self.is_empty():
return 0
count = 1
current_node = self.head
while current_node.next != self.head:
count += 1
current_node = current_node.next
return count
def traverse(self):
"""
遍历链表
时间复杂度: O(n)
"""
if self.is_empty():
print("链表为空")
return
result = []
current_node = self.head
# 循环的终止条件是再次回到头节点
while True:
result.append(str(current_node.data))
current_node = current_node.next
if current_node == self.head:
break
print(" -> ".join(result) + " -> (Head)")
1. 插入操作
python
def insert_at_head(self, data):
"""
在链表头部插入一个新节点
时间复杂度: O(n),因为需要找到尾节点并修改其 next 指针
"""
new_node = Node(data)
if self.is_empty():
# 如果链表为空,新节点的 next 指向自己
new_node.next = new_node
self.head = new_node
else:
# 找到尾节点
tail_node = self.head
while tail_node.next != self.head:
tail_node = tail_node.next
new_node.next = self.head
self.head = new_node
tail_node.next = self.head # 尾节点指向新的头节点
self.size += 1
def insert_at_tail(self, data):
"""
在链表尾部插入一个新节点
时间复杂度: O(n),因为需要找到尾节点
"""
new_node = Node(data)
if self.is_empty():
new_node.next = new_node
self.head = new_node
else:
# 找到尾节点
tail_node = self.head
while tail_node.next != self.head:
tail_node = tail_node.next
tail_node.next = new_node
new_node.next = self.head # 新的尾节点指向头节点
self.size += 1
2. 删除操作
python
def delete_at_head(self):
"""
删除链表头部的节点
时间复杂度: O(n),因为需要找到尾节点并修改其 next 指针
"""
if self.is_empty():
print("链表为空,无法删除")
return None
deleted_data = self.head.data
if self.head.next == self.head: # 如果只有一个节点
self.head = None
else:
# 找到尾节点
tail_node = self.head
while tail_node.next != self.head:
tail_node = tail_node.next
self.head = self.head.next
tail_node.next = self.head # 尾节点指向新的头节点
self.size -= 1
return deleted_data
三、循环链表的优缺点
优点
- 环形遍历:适合需要反复循环处理数据的场景,例如操作系统中的进程调度(使用时间片轮转法)。
- 从任意节点开始访问:提高了访问的灵活性。
- 简化某些操作 :在某些情况下,可以简化链表的操作,例如不需要判断
current_node.next是否为null。
缺点
- 遍历终止条件复杂 :遍历链表时,不能简单地以
current_node is None作为终止条件,必须判断是否回到了起始节点,否则会导致死循环。 - 实现相对复杂:相比于普通链表,循环链表的插入、删除等操作需要额外注意维护环形结构,尤其是处理头节点和尾节点时。
- 内存开销:与普通链表相同,需要额外存储指针。
四、循环链表的应用场景
- 操作系统的进程调度:多个进程循环等待 CPU 时间片。
- 约瑟夫环问题 (Josephus Problem):这是一个经典的算法问题,其描述为:一群人围坐成一圈,从某个人开始报数,报到某个数的人出列,接着从下一个人开始继续报数,如此反复,直到所有人都出列。最后出列的人或特定顺序的人是胜利者。这个问题非常适合用循环链表来模拟。
- 资源循环利用:例如,在一个缓冲区中,数据被循环写入和读取。
- 某些游戏场景:例如,玩家围成一圈进行游戏。
五、总结
循环链表是一种特殊的链表结构,其核心特征是形成一个闭合的环。它在需要循环遍历或从任意节点开始访问的场景中非常有用。然而,其遍历终止条件和操作的复杂性也需要特别注意。在实际应用中,需根据具体需求选择合适的链表类型。