数据结构——链表
链表(Linked List)是一种常见的基础数据结构,它由一系列节点(Node)构成,每个节点包含两个部分:
- 数据域(Data):存储数据的部分。
- 指针域(Next):指向链表中下一个节点的引用或指针。
链表是一种动态数据结构,与数组不同,链表的元素不需要在内存中是连续的,它们通过指针连接形成一个链式结构。
1. 链表的基本概念
1.1. 节点结构
一个链表由多个节点组成,每个节点包含数据和指向下一个节点的指针。典型的节点结构如下:
class ListNode:
def __init__(self, val=0, next=None):
self.val = val # 节点的值
self.next = next # 指向下一个节点的指针
1.2. 链表的基本操作
链表的操作主要包括:
- 插入操作:在指定位置插入新的节点。
- 删除操作:删除指定位置的节点。
- 查找操作:查找链表中某个节点。
- 遍历操作:访问链表中的所有节点。
2. 链表的类型
链表有多种不同类型,最常见的有以下几种:
2.1. 单链表(Singly Linked List)
每个节点只包含一个指向下一个节点的指针。链表的最后一个节点的 next
指针指向 None
。
头节点 -> 节点1 -> 节点2 -> 节点3 -> ... -> 节点n -> None
2.2. 双链表(Doubly Linked List)
每个节点包含两个指针,一个指向下一个节点,另一个指向前一个节点。双链表可以双向遍历。
None <- 节点1 <-> 节点2 <-> 节点3 <-> ... <-> 节点n -> None
2.3. 循环链表(Circular Linked List)
链表的最后一个节点指向头节点,而不是 None
。可以是单向或双向循环链表。
头节点 -> 节点1 -> 节点2 -> ... -> 节点n -> 头节点(循环)
3. 单链表的操作
3.1. 插入操作
- 在头部插入节点:
插入一个新节点到链表的头部,更新头指针。
def insert_at_head(head, val):
new_node = ListNode(val)
new_node.next = head
return new_node # 返回新的头节点
- 在尾部插入节点:
遍历到链表的尾部,插入一个新的节点。
def insert_at_tail(head, val):
new_node = ListNode(val)
if not head: # 链表为空,直接将新节点作为头节点
return new_node
current = head
while current.next:
current = current.next
current.next = new_node # 将尾节点的指针指向新节点
return head
- 在指定位置插入节点:
在链表的指定位置插入一个节点。
def insert_at_position(head, val, position):
new_node = ListNode(val)
if position == 0: # 在头部插入
new_node.next = head
return new_node
current = head
count = 0
while current and count < position - 1:
current = current.next
count += 1
if current is None: # 如果位置超过链表长度,返回原链表
return head
new_node.next = current.next
current.next = new_node
return head
3.2. 删除操作
- 删除头节点:
删除链表的第一个节点,更新头指针。
def delete_head(head):
if head is None:
return None # 链表为空,返回空
return head.next # 返回新的头节点
- 删除指定节点:
删除链表中某个位置的节点。
def delete_at_position(head, position):
if position == 0: # 删除头节点
return delete_head(head)
current = head
count = 0
while current and count < position - 1:
current = current.next
count += 1
if current is None or current.next is None: # 如果位置不合法
return head
current.next = current.next.next # 删除指定位置的节点
return head
3.3. 查找操作
- 查找指定值的节点:
遍历链表,查找值为val
的节点。
def find_node(head, val):
current = head
while current:
if current.val == val:
return current
current = current.next
return None # 如果没有找到,返回 None
3.4. 遍历操作
遍历整个链表,打印每个节点的值。
def print_linked_list(head):
current = head
while current:
print(current.val, end=" -> ")
current = current.next
print("None")
4. 链表的优缺点
4.1. 优点
- 动态大小:链表不需要预分配内存空间,可以在运行时动态地调整大小。
- 高效的插入和删除操作:在链表中插入或删除节点不需要移动大量元素,时间复杂度为
O(1)
。 - 内存利用率高:节点按需分配内存,节省了内存空间。
4.2. 缺点
- 随机访问效率低:链表无法直接访问任意节点,需要通过遍历来查找,时间复杂度为
O(n)
。 - 额外的空间开销:每个节点除了存储数据外,还需要额外的指针域,占用更多内存。
- 指针复杂度高:在复杂操作中,指针的维护和管理容易出错,增加了程序的复杂性。
5. 链表的应用
链表作为一种基本数据结构,广泛应用于许多场景中:
- 实现栈和队列:链表是实现栈(LIFO)和队列(FIFO)数据结构的常见选择。
- 内存管理:操作系统中的动态内存分配常使用链表来管理空闲内存块。
- 图的邻接表表示:链表可以用于表示图的邻接表,其中每个节点存储与某个节点相邻的其他节点。
- LRU 缓存:链表也常用于实现 LRU(Least Recently Used)缓存,按需删除最不常用的元素。
6. 总结
链表是一种灵活的动态数据结构,通过节点间的链接实现了灵活的插入和删除操作。尽管链表的随机访问效率较低,但它在实现栈、队列、图的邻接表等应用中仍然发挥着重要作用。掌握链表的基本操作和应用可以帮助我们解决许多实际问题,并为理解更复杂的数据结构打下基础。
发表回复