数据结构第一弹-链表

151 阅读5分钟

大家好,今天和大家一起分享一下数据结构中的链表~

链表是一种常见的线性数据结构,它通过指针将一系列节点连接起来形成一个序列。与数组不同,链表中的元素不必存储在连续的内存位置上,这使得链表在插入和删除操作上更加灵活。今天详细介绍链表的基本概念、类型、实现方式以及实际应用。

一、链表简介

链表是由一系列节点组成的数据结构,每个节点包含数据部分(可以是一个简单值或复杂对象)和指向下一个节点的引用(指针)。根据节点间链接方式的不同,链表可以分为单向链表、双向链表和循环链表等几种类型。

  • 单向链表:每个节点只含有一个指向后继节点的链接。
  • 双向链表:每个节点除了含有指向后继节点的链接外,还含有一个指向前驱节点的链接。
  • 循环链表:最后一个节点的链接指向头节点,形成一个环。

二、链表的优势与劣势

链表的主要优势在于其高效的插入和删除操作。由于不需要移动其他元素,这些操作的时间复杂度为O(1)。然而,在访问特定元素时,链表通常需要从头开始遍历整个列表,因此访问时间复杂度为O(n),这比数组要慢得多。

优点:

  • 插入和删除操作高效。
  • 动态分配内存,无需预先指定大小。
  • 不需要连续的内存空间。

缺点:

  • 随机访问效率低。
  • 使用额外的空间来存储指针。
  • 较难实现某些操作,如排序和查找。

三、链表的操作

链表支持多种基本操作,包括但不限于创建、插入、删除、遍历和查找等。下面我们将详细介绍每种操作的具体实现方法。

3.1 创建链表

创建链表通常涉及定义一个节点类和初始化链表的过程。节点类至少包含数据域和指向下一个节点的指针。

class ListNode:
    def __init__(self, value=0, next=None):
        self.value = value
        self.next = next

3.2 插入节点

插入节点可以发生在链表的头部、尾部或者任意位置。我们以Python为例,展示如何在链表的头部插入新节点。

def insert_at_head(head, value):
    new_node = ListNode(value)
    new_node.next = head
    return new_node  # 返回新的头节点

如果要在链表中间或尾部插入节点,则需要先找到正确的插入位置。

def insert_after(node, value):
    if node is None# 如果给定的节点为空,则无法插入
        return
    new_node = ListNode(value)
    new_node.next = node.next
    node.next = new_node

3.3 删除节点

删除节点同样可以根据目标节点的位置进行。例如,删除链表的第一个节点:

def delete_head(head):
    if head is None# 如果链表为空,则无法删除
        return None
    return head.next  # 返回新的头节点

若要删除链表中的某个特定节点,则需要找到该节点及其前一个节点。

def delete_node(prev, target):
    if prev is None or prev.next != target:  # 确保prev和target有效
        return
    prev.next = target.next  # 跳过target节点

3.4 遍历链表

遍历链表意味着从头节点开始,依次访问每个节点直到链表结束。这是执行其他操作的基础步骤。

def traverse_list(head):
    current = head
    while current is not None:
        print(current.value, end=" -> ")
        current = current.next
    print("None")

3.5 查找节点

查找特定值的节点通常需要遍历整个链表。如果找到了匹配的节点,则返回该节点;否则返回None。

def find_node(head, value):
    current = head
    while current is not None:
        if current.value == value:
            return current
        current = current.next
    return None

四、链表的应用场景

链表因其灵活性被广泛应用于各种场合,比如实现栈、队列、图的邻接表表示法等。此外,链表也是许多高级数据结构的基础,如哈希表中的桶、树的子节点列表等。

实战案例:LRU缓存

最近最少使用(Least Recently Used, 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: int):
        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 get(self, key: int) -> int:
        if key in self.cache:
            node = self.cache[key]
            self._remove(node)
            self._add(node)
            return node.value
        return -1

    def put(self, key: int, value: int) -> None:
        if key in self.cache:
            self._remove(self.cache[key])
        node = self.Node(key, value)
        self.cache[key] = node
        self._add(node)
        if len(self.cache) > self.capacity:
            lru = self.head.next
            self._remove(lru)
            del self.cache[lru.key]

    def _remove(self, node):
        prev = node.prev
        next_ = node.next
        prev.next = next_
        next_.prev = prev

    def _add(self, node):
        prev = self.tail.prev
        prev.next = node
        self.tail.prev = node
        node.prev = prev
        node.next = self.tail


# 示例用法
cache = LRUCache(2)
cache.put(1, 1)
cache.put(2, 2)
print(cache.get(1))  # 返回 1
cache.put(3, 3)      # 该操作会使得键 2 作废
print(cache.get(2))  # 返回 -1 (未找到)
cache.put(4, 4)      # 该操作会使得键 1 作废
print(cache.get(1))  # 返回 -1 (未找到)
print(cache.get(3))  # 返回 3
print(cache.get(4))  # 返回 4


链表作为一种重要的数据结构,在处理动态数据集合时具有独特的优势。通过理解链表的工作原理及其常见操作,我们可以更有效地利用它来解决实际问题,欢迎大家一起沟通交流~****