【数据结构与算法】最适合新手小白的教程——链表初步(包你看懂!)

hello大家好!好久不见,也是有点久没有更新了,最近一直在做一个nlp的项目抽不开身,两个模型一跑就是几天,真是震撼我了,不知道大家跑模型的时候是不是跟我一样哈~

今天的内容主要讲的是链表结构里面算法相关的,如果是代码类型的就没有怎么提到。还是老样子,多画图多去思考,很多问题就能迎刃而解

好啦,咱们话不多说,进入今天的主题吧~

一、链表介绍

链表是一种基础的数据结构,由一系列节点(Node)组成,每个节点包含两个部分:一个数据域(存储实际的数据)和一个或多个指针域(存储指向其他节点的引用)。链表通过这些指针将节点连接成一个线性序列。根据指针的数量和连接方向,链表可以分为以下几种类型:

1. 单链表(Singly Linked List)

  • 结构:每个节点只包含一个指向下一个节点的指针。
  • 特点:单向链接,只能从头节点开始顺序访问到最后一个节点,不能反向访问。
  • 应用场景:常用于实现简单的数据集合,提供动态插入和删除操作。

2. 双向链表(Doubly Linked List)

  • 结构:每个节点包含两个指针,一个指向下一个节点,一个指向前一个节点。
  • 特点:可以在两个方向上遍历链表(从头到尾或从尾到头),比单链表更灵活,但占用更多内存。
  • 应用场景:适合需要双向遍历或在任意位置快速插入/删除的情况,如浏览器的前进和后退功能。

3. 循环链表(Circular Linked List)

  • 结构:链表中的最后一个节点指向头节点,形成一个环状结构。
  • 特点:没有明确的开始和结束点,可以从任意一个节点开始遍历整个链表。
  • 应用场景:适用于需要循环访问数据的情况,如实现环形缓冲区、循环队列等。

链表的优点

  1. 动态内存分配:链表节点在运行时动态分配,节省内存空间,避免了数组需要预分配固定大小的问题。
  2. 插入和删除操作方便:在链表中插入或删除节点不需要移动其他节点,只需调整相邻节点的指针即可。
  3. 灵活的存储结构:链表中的元素可以分散存储,不需要连续的内存空间。

二、链表基本操作

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 指向头节点。

#遍历整个链表,对于每个节点,交换其 nextprev 指针。

#最后,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: 定义了一个简单的链表节点类,每个节点包含 datanext 两个属性

  • is_palindrome 函数:

    • 使用快指针 fast 和慢指针 slow 遍历链表。fast 每次移动两步,slow 每次移动一步。
    • 在移动过程中,使用 prev 指针将链表的前半部分反转。具体操作是将 slow.next 指向 prev,并逐步推进 prevslow 指针。
    • fast 指针到达链表末尾时,slow 指针到达中间节点。如果链表长度为奇数,跳过中间节点。
    • 接下来,使用 prevslow 同时遍历反转的前半部分和原来的后半部分。如果对应节点的值都相等,则链表是回文;否则,不是回文。
  • print_list 函数: 用于输出链表的节点值,方便查看链表结构。

  • 测试代码: 创建一个回文链表 1 -> 2 -> 3 -> 2 -> 1,调用 is_palindrome 函数判断是否为回文,并输出结果。

三、小结

链表的题目其实有非常多,只是篇幅有限,这里也没办法全都写出,后面会一题一题的出博客讲解吧,这些题目都是力扣上面查得到的,大家有兴趣可以去看看~

这段时间事情太多了,所以更新博客没有那么快,大家见谅QAQ

更新博客我是不会断的,只是内容我现在自己也不太确定,而且马上开学了,比赛加上上课事情还不少,容我再思考思考QAQ

好啦,本期文章就到这里,有问题的小伙伴可以评论区打出,我看到会马上回复哒~

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值