数据结构:循环链表

概述

  • 循环链表是一种特殊的链表,它的最后一个节点的 next 指针不是指向 null,而是指向链表的头节点(对于单循环链表),或者尾节点的 next 指针指向头节点,同时头节点的 prev 指针指向尾节点(对于双循环链表)。
  • 这种结构形成了一个闭合的环,使得从任何一个节点出发都能遍历整个链表。
  • 资料:https://pan.quark.cn/s/43d906ddfa1b

一、循环链表的结构特点

  1. 闭合环形结构:链表中没有明确的"末尾"节点,最后一个节点与头节点相连。
  2. 遍历无边界:从任意节点开始,沿着指针方向可以无限循环遍历所有节点(需要注意遍历的终止条件,否则会陷入死循环)。
  3. 动态大小:和普通链表一样,可以动态地添加或删除节点。
  4. 可从任意节点访问:由于是环形,访问链表元素不一定要从头节点开始。

循环链表主要分为两种:

  • 单循环链表 (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

三、循环链表的优缺点

优点
  1. 环形遍历:适合需要反复循环处理数据的场景,例如操作系统中的进程调度(使用时间片轮转法)。
  2. 从任意节点开始访问:提高了访问的灵活性。
  3. 简化某些操作 :在某些情况下,可以简化链表的操作,例如不需要判断 current_node.next 是否为 null
缺点
  1. 遍历终止条件复杂 :遍历链表时,不能简单地以 current_node is None 作为终止条件,必须判断是否回到了起始节点,否则会导致死循环。
  2. 实现相对复杂:相比于普通链表,循环链表的插入、删除等操作需要额外注意维护环形结构,尤其是处理头节点和尾节点时。
  3. 内存开销:与普通链表相同,需要额外存储指针。

四、循环链表的应用场景

  1. 操作系统的进程调度:多个进程循环等待 CPU 时间片。
  2. 约瑟夫环问题 (Josephus Problem):这是一个经典的算法问题,其描述为:一群人围坐成一圈,从某个人开始报数,报到某个数的人出列,接着从下一个人开始继续报数,如此反复,直到所有人都出列。最后出列的人或特定顺序的人是胜利者。这个问题非常适合用循环链表来模拟。
  3. 资源循环利用:例如,在一个缓冲区中,数据被循环写入和读取。
  4. 某些游戏场景:例如,玩家围成一圈进行游戏。

五、总结

循环链表是一种特殊的链表结构,其核心特征是形成一个闭合的环。它在需要循环遍历或从任意节点开始访问的场景中非常有用。然而,其遍历终止条件和操作的复杂性也需要特别注意。在实际应用中,需根据具体需求选择合适的链表类型。

相关推荐
sin_hielo10 分钟前
leetcode 2110
数据结构·算法·leetcode
panzer_maus28 分钟前
归并排序的简单介绍
java·数据结构·算法
摆烂且佛系42 分钟前
B+树的“页分裂“机制
数据结构·b树
福尔摩斯张2 小时前
C++核心特性精讲:从C语言痛点出发,掌握现代C++编程精髓(超详细)
java·linux·c语言·数据结构·c++·驱动开发·算法
历程里程碑3 小时前
C++ 9 stack_queue:数据结构的核心奥秘
java·开发语言·数据结构·c++·windows·笔记·算法
仰泳的熊猫3 小时前
1108 Finding Average
数据结构·c++·算法·pat考试
炽烈小老头3 小时前
【每天学习一点算法 2025/12/15】环形链表
学习·算法·链表
晨晖24 小时前
顺序栈的入栈函数
数据结构
hweiyu005 小时前
数据结构:后缀自动机
数据结构
小尧嵌入式5 小时前
C语言中的面向对象思想
c语言·开发语言·数据结构·c++·单片机·qt