Python实现单链表与循环链表的核心操作与应用

📅 2026/8/5 11:29:18
Python实现单链表与循环链表的核心操作与应用
1. 链表基础概念与单链表实现链表作为数据结构中的经典类型与数组有着本质区别。数组在内存中是连续存储的而链表的每个元素称为节点可以分散在内存各处通过指针相互连接。这种非连续特性让链表在插入和删除操作上具有天然优势。单链表是最简单的链表形式每个节点包含两个部分数据域存储实际数据指针域存储下一个节点的内存地址用Python实现单链表节点类如下class Node: def __init__(self, data): self.data data # 数据域 self.next None # 指针域单链表的常见操作时间复杂度分析访问O(n) - 必须从头节点开始逐个遍历插入/删除O(1) - 只需修改相邻节点的指针搜索O(n) - 需要遍历整个链表注意虽然插入操作本身是O(1)但找到插入位置可能需要O(n)时间所以实际应用中要区分操作本身和前置查找的时间成本2. 单链表的核心操作实现2.1 基础操作实现单链表的五大基础操作需要特别注意指针处理的顺序否则容易造成内存泄漏或链表断裂头插法建立链表def insert_at_head(head, data): new_node Node(data) new_node.next head return new_node # 新节点成为新的头节点尾插法建立链表需要维护尾指针def insert_at_tail(head, data): new_node Node(data) if not head: return new_node current head while current.next: # 找到最后一个节点 current current.next current.next new_node return head删除节点需处理头节点特殊情况def delete_node(head, key): # 处理头节点就是要删除的节点的情况 while head and head.data key: head head.next current head while current and current.next: if current.next.data key: current.next current.next.next # 跳过待删除节点 else: current current.next return head2.2 单链表逆序操作链表逆序是面试高频考点需要熟练掌握迭代和递归两种实现方式迭代法推荐def reverse_iterative(head): prev None current head while current: next_node current.next # 临时保存下一个节点 current.next prev # 反转指针 prev current # 前驱节点后移 current next_node # 当前节点后移 return prev # 新的头节点递归法理解指针变化def reverse_recursive(head): if not head or not head.next: return head new_head reverse_recursive(head.next) head.next.next head # 反转指针 head.next None # 断开原指针 return new_head实际工程中发现当链表长度超过1000时递归实现可能导致栈溢出因此生产环境推荐使用迭代法3. 循环链表的特性与应用3.1 循环链表基本结构循环链表是单链表的变体区别在于尾节点的指针不是指向None而是指向头节点形成一个环。这种结构特别适合需要循环访问的场景。循环链表的Python实现关键点class CircularLinkedList: def __init__(self): self.head None def append(self, data): new_node Node(data) if not self.head: self.head new_node new_node.next self.head # 自环 else: current self.head while current.next ! self.head: # 判断是否回到头节点 current current.next current.next new_node new_node.next self.head3.2 循环链表的优势场景轮询任务调度操作系统中的轮询调度算法多人游戏回合制玩家轮流操作的实现缓冲区管理循环缓冲区Ring Buffer的实现约瑟夫问题经典数学问题的理想数据结构循环链表的遍历需要特别注意终止条件否则会进入无限循环def print_list(head): if not head: return current head while True: print(current.data, end ) current current.next if current head: # 回到起点则终止 break4. 链表实战问题解析4.1 链表中的快慢指针技巧快慢指针是解决链表问题的利器典型应用包括检测循环Floyd判圈算法def has_cycle(head): slow fast head while fast and fast.next: slow slow.next fast fast.next.next if slow fast: return True return False寻找链表中点用于归并排序def find_middle(head): slow fast head while fast and fast.next: slow slow.next fast fast.next.next return slow寻找循环入口点数学推导得出def detect_cycle_start(head): slow fast head while fast and fast.next: slow slow.next fast fast.next.next if slow fast: break if not fast or not fast.next: return None slow head while slow ! fast: slow slow.next fast fast.next return slow4.2 工程中的链表优化实践在实际项目中纯链表的应用往往会进行一些优化带尾指针的链表提升尾部操作效率双向链表支持双向遍历虽然增加空间开销跳表Skip ListRedis中的有序集合实现方式结合哈希表实现LRU缓存机制以LRU缓存实现为例展示链表与哈希表的结合class LRUCache: class Node: def __init__(self, key, value): self.key key self.value value self.prev None self.next None def __init__(self, capacity): self.capacity capacity self.cache {} self.head self.Node(0, 0) # 伪头节点 self.tail self.Node(0, 0) # 伪尾节点 self.head.next self.tail self.tail.prev self.head def _add_node(self, node): # 总是添加到头部 node.prev self.head node.next self.head.next self.head.next.prev node self.head.next node def _remove_node(self, node): prev node.prev new node.next prev.next new new.prev prev def _move_to_head(self, node): self._remove_node(node) self._add_node(node) def get(self, key): if key in self.cache: node self.cache[key] self._move_to_head(node) return node.value return -1 def put(self, key, value): if key in self.cache: node self.cache[key] node.value value self._move_to_head(node) else: if len(self.cache) self.capacity: # 移除尾部节点 tail self.tail.prev self._remove_node(tail) del self.cache[tail.key] new_node self.Node(key, value) self.cache[key] new_node self._add_node(new_node)链表操作中最容易犯的错误是指针丢失。在插入节点时一定要先保存后续节点的指针再进行修改。例如在反转链表时我们先用next_node保存current.next然后再修改current.next指向prev。如果顺序搞反就会丢失对后续节点的引用。另一个常见误区是循环链表的遍历终止条件。不同于普通链表用while current来判断结束循环链表需要用while current ! head作为终止条件否则会进入无限循环。我在实际项目中就曾因为这个问题导致服务CPU飙高最终通过添加循环计数器发现了这个问题。