hello大家好!好久不见,也是有点久没有更新了,最近一直在做一个nlp的项目抽不开身,两个模型一跑就是几天,真是震撼我了,不知道大家跑模型的时候是不是跟我一样哈~
今天的内容主要讲的是链表结构里面算法相关的,如果是代码类型的就没有怎么提到。还是老样子,多画图多去思考,很多问题就能迎刃而解
好啦,咱们话不多说,进入今天的主题吧~
一、链表介绍
链表是一种基础的数据结构,由一系列节点(Node)组成,每个节点包含两个部分:一个数据域(存储实际的数据)和一个或多个指针域(存储指向其他节点的引用)。链表通过这些指针将节点连接成一个线性序列。根据指针的数量和连接方向,链表可以分为以下几种类型:
1. 单链表(Singly Linked List)
- 结构:每个节点只包含一个指向下一个节点的指针。
- 特点:单向链接,只能从头节点开始顺序访问到最后一个节点,不能反向访问。
- 应用场景:常用于实现简单的数据集合,提供动态插入和删除操作。
2. 双向链表(Doubly Linked List)
- 结构:每个节点包含两个指针,一个指向下一个节点,一个指向前一个节点。
- 特点:可以在两个方向上遍历链表(从头到尾或从尾到头),比单链表更灵活,但占用更多内存。
- 应用场景:适合需要双向遍历或在任意位置快速插入/删除的情况,如浏览器的前进和后退功能。
3. 循环链表(Circular Linked List)
- 结构:链表中的最后一个节点指向头节点,形成一个环状结构。
- 特点:没有明确的开始和结束点,可以从任意一个节点开始遍历整个链表。
- 应用场景:适用于需要循环访问数据的情况,如实现环形缓冲区、循环队列等。
链表的优点
- 动态内存分配:链表节点在运行时动态分配,节省内存空间,避免了数组需要预分配固定大小的问题。
- 插入和删除操作方便:在链表中插入或删除节点不需要移动其他节点,只需调整相邻节点的指针即可。
- 灵活的存储结构:链表中的元素可以分散存储,不需要连续的内存空间。
二、链表基本操作
1、反转单向和双向链表
要求:链表长度为N,时间复杂度要求为O(N),额外空间复杂度要求为O(1)
# 单向链表
# 定义节点类
class Node:
def __init__(self, data):
self.data = data
self.next = None
# 反转单向链表
def reverse_singly_linked_list(head):
prev = None
current = head
while current is not None:
next_node = current.next # 暂存当前节点的下一个节点
current.next = prev # 反转当前节点的指针方向
prev = current # 将 prev 指向当前节点
current = next_node # 将 current 指向下一个节点
return prev # prev 成为新的头节点
# 测试单向链表反转,打印列表
def print_list(head):
current = head
while current:
print(current.data, end=" -> ")
current = current.next
print("None")
if __name__ == "__main__":
# 创建一个单向链表: 1 -> 2 -> 3 -> 4 -> None
head = Node(1)
head.next = Node(2)
head.next.next = Node(3)
head.next.next.next = Node(4)
print("原始单向链表:")
print_list(head) # 输出: 1 -> 2 -> 3 -> 4 -> None
head = reverse_singly_linked_list(head)
print("反转后的单向链表:")
print_list(head) # 输出: 4 -> 3 -> 2 -> 1 -> None
#初始化三个指针:prev
(前驱节点)、current
(当前节点)、next
(后继节点)。
#遍历整个链表,将每个节点的指针反转。
#最后,prev
指针将指向新链表的头部。
# 定义双向链表节点类
class DNode:
def __init__(self, data):
self.data = data
self.next = None
self.prev = None
# 反转双向链表
def reverse_doubly_linked_list(head):
current = head
while current is not None:
# 交换 next 和 prev 指针
temp = current.next
current.next = current.prev
current.prev = temp
# 更新当前节点为下一个节点
head = current # 更新 head 为当前节点
current = temp
return head
# 测试双向链表反转
def print_doubly_list(head):
current = head
while current:
print(current.data, end=" <-> ")
current = current.next
print("None")
if __name__ == "__main__":
# 创建一个双向链表: 1 <-> 2 <-> 3 <-> 4 <-> None
head = DNode(1)
node2 = DNode(2)
node3 = DNode(3)
node4 = DNode(4)
head.next = node2
node2.prev = head
node2.next = node3
node3.prev = node2
node3.next = node4
node4.prev = node3
print("原始双向链表:")
print_doubly_list(head) # 输出: 1 <-> 2 <-> 3 <-> 4 <-> None
head = reverse_doubly_linked_list(head)
print("反转后的双向链表:")
print_doubly_list(head) # 输出: 4 <-> 3 <-> 2 <-> 1 <-> None
#初始化指针 current
指向头节点。
#遍历整个链表,对于每个节点,交换其 next
和 prev
指针。
#最后,prev
指针指向新链表的头部。
2、打印两个有序列表的公共部分
要求:两个链表长度之和为N,时间复杂度要求为O(N),额外空间复杂度要求为O(1)
方法:两个指针,同时在链表上从头结点开始移动,谁节点上的数小谁移动,相等就打印值,节点为null就停下
这个方法挺简单的,代码就留给大家自己去实现吧~
3、判断回文结构
要求:链表长度为N,时间复杂度要求为O(N),额外空间复杂度要求为O(1),形如1->2->1时返回true
方法:以node(节点)为最小单位去对应值的大小,从而判断是否回文,使用快慢指针,根据链表长度判断指针步数差值,走完后给慢指针位置做标记,快指针逆序往回走,慢指针从头开始走,如果每一步都相等,则是回文结构
# 定义节点类
class Node:
def __init__(self, data):
self.data = data
self.next = None
# 判断链表是否是回文
def is_palindrome(head):
if not head or not head.next:
return True # 空链表或只有一个节点的链表是回文
# 使用快慢指针找到链表中点
slow = head
fast = head
prev = None # 用于反转链表前半部分
while fast and fast.next:
fast = fast.next.next # 快指针每次走两步
# 反转链表前半部分
next_node = slow.next # 保存下一个节点
slow.next = prev # 反转指针
prev = slow # 移动 prev 指针
slow = next_node # 移动 slow 指针
# 如果链表长度为奇数,跳过中间节点
if fast:
slow = slow.next
# 比较反转后的前半部分与后半部分
while prev and slow:
if prev.data != slow.data:
return False # 值不相等,不是回文
prev = prev.next # 继续比较前半部分
slow = slow.next # 继续比较后半部分
return True # 所有值都相等,是回文
# 打印链表
def print_list(head):
current = head
while current:
print(current.data, end=" -> ")
current = current.next
print("None")
# 测试
if __name__ == "__main__":
# 创建一个回文链表: 1 -> 2 -> 3 -> 2 -> 1
head = Node(1)
head.next = Node(2)
head.next.next = Node(3)
head.next.next.next = Node(2)
head.next.next.next.next = Node(1)
print("链表:")
print_list(head) # 输出: 1 -> 2 -> 3 -> 2 -> 1 -> None
if is_palindrome(head):
print("链表是回文")
else:
print("链表不是回文")
# 节点类 Node
: 定义了一个简单的链表节点类,每个节点包含 data
和 next
两个属性
-
is_palindrome
函数:- 使用快指针
fast
和慢指针slow
遍历链表。fast
每次移动两步,slow
每次移动一步。 - 在移动过程中,使用
prev
指针将链表的前半部分反转。具体操作是将slow.next
指向prev
,并逐步推进prev
和slow
指针。 - 当
fast
指针到达链表末尾时,slow
指针到达中间节点。如果链表长度为奇数,跳过中间节点。 - 接下来,使用
prev
和slow
同时遍历反转的前半部分和原来的后半部分。如果对应节点的值都相等,则链表是回文;否则,不是回文。
- 使用快指针
-
print_list
函数: 用于输出链表的节点值,方便查看链表结构。 -
测试代码: 创建一个回文链表
1 -> 2 -> 3 -> 2 -> 1
,调用is_palindrome
函数判断是否为回文,并输出结果。
三、小结
链表的题目其实有非常多,只是篇幅有限,这里也没办法全都写出,后面会一题一题的出博客讲解吧,这些题目都是力扣上面查得到的,大家有兴趣可以去看看~
这段时间事情太多了,所以更新博客没有那么快,大家见谅QAQ
更新博客我是不会断的,只是内容我现在自己也不太确定,而且马上开学了,比赛加上上课事情还不少,容我再思考思考QAQ
好啦,本期文章就到这里,有问题的小伙伴可以评论区打出,我看到会马上回复哒~