大火的人工智能,从Python链表说起

158 阅读5分钟

首先,让我们来看一下链表是什么。链表是一种数据结构,它由一系列节点组成,每个节点包含数据和指向下一个节点的指针。链表的好处是可以动态地添加或删除节点,而不需要像数组一样预先分配足够的空间。

接下来,让我们来看一些Python代码示例,来了解如何使用链表。

首先,我们需要定义一个节点类,它包含数据和指向下一个节点的指针:

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

然后,我们可以定义一个链表类,它包含指向链表头部的指针和一些基本操作,如添加节点、删除节点等:

class LinkedList:
    def __init__(self):
        self.head = None

    def add_node(self, data):
        new_node = Node(data)
        if self.head is None:
            self.head = new_node
        else:
            current = self.head
            while current.next is not None:
                current = current.next
            current.next = new_node

    def remove_node(self, data):
        if self.head is None:
            return
        if self.head.data == data:
            self.head = self.head.next
            return
        current = self.head
        while current.next is not None:
            if current.next.data == data:
                current.next = current.next.next
                return
            current = current.next

最后,让我们来看一些实际的用例。比如,我们可以使用链表来实现 LRU 缓存淘汰算法。LRU 缓存淘汰算法是一种常用的缓存淘汰策略,它会删除最近最少使用的缓存项,以释放空间。

以下是一个简单的 LRU 缓存实现,使用了一个字典和一个双向链表:

class LRUCache:
    def __init__(self, capacity):
        self.capacity = capacity
        self.cache = {}
        self.head = Node(0)
        self.tail = Node(0)
        self.head.next = self.tail
        self.tail.prev = self.head

    def _remove_node(self, node):
        node.prev.next = node.next
        node.next.prev = node.prev

    def _add_node(self, node):
        node.prev = self.head
        node.next = self.head.next
        self.head.next.prev = node
        self.head.next = node

    def get(self, key):
        if key in self.cache:
            node = self.cache[key]
            self._remove_node(node)
            self._add_node(node)
            return node.value
        return -1

    def put(self, key, value):
        if key in self.cache:
            self._remove_node(self.cache[key])
        node = Node(value)
        self.cache[key] = node
        self._add_node(node)
        if len(self.cache) > self.capacity:
            del self.cache[self.tail.prev.key]
            self._remove_node(self.tail.prev)

让我们结合一些 LeetCode 上的单向链表的题目来说明如何使用单向链表。

LeetCode 上一些关于单向链表的经典题目包括:

下面我们分别介绍这些问题以及如何使用单向链表来解决它们。

  1. 反转链表(Reverse Linked List)

这是一个非常基础的单向链表问题。我们需要将给定链表翻转,使得链表的最后一个节点变为第一个节点,倒数第二个节点变为第二个节点,以此类推。

我们可以使用一个 prev 节点来记录当前节点的前一个节点,一个 curr 节点来记录当前节点,以及一个 next 节点来记录当前节点的下一个节点。我们遍历链表,将当前节点的 next 指向 prev,然后将 prev 和 curr 分别向后移动一个节点即可。

代码如下:

def reverse_list(head):
    prev = None
    curr = head
    while curr:
        next_node = curr.next
        curr.next = prev
        prev = curr
        curr = next_node
    return prev
  1. 环形链表(Linked List Cycle)

这个问题要求我们判断一个单向链表是否存在环,即链表中是否存在一个节点可以一直遍历下去而不会到达尾部。

我们可以使用快慢指针的方法来解决这个问题。我们定义两个指针,一个快指针和一个慢指针,从链表的头部开始遍历。快指针每次向前移动两个节点,而慢指针每次只向前移动一个节点。如果链表中存在环,那么这两个指针最终会相遇。

代码如下:

def has_cycle(head):
    slow = head
    fast = head
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
        if slow == fast:
            return True
    return False
  1. 合并两个有序链表(Merge Two Sorted Lists

这个问题要求我们将两个有序的单向链表合并成一个新的有序链表。

我们可以使用递归来解决这个问题。我们首先比较两个链表的头部节点,将较小的节点作为新链表的头部,并将新链表的 next 指向递归调用合并后的链表。如果有一个链表为空了,我们直接返回另一个链表作为新链表的尾部即可。

代码如下:

def merge_two_lists(l1, l2):
    if not l1:
        return l2
    if not l2:
        return l1
    if l1.val < l2.val:
        l1.next = merge_two_lists(l1.next, l2)
        return l1
    else:
        l2.next = merge_two_lists(l1, l2.next)
        return l2
  1. 删除排序链表中的重复元素(Remove Duplicates from Sorted List)

这个问题要求我们删除一个已经排序的单向链表中的重复节点。

我们可以使用双指针的方法来解决这个问题。我们定义一个 prev 指针和一个 curr 指针,从链表的头部开始遍历。当 prev 和 curr 指向的节点的值相同时,我们将 curr 向后移动一个节点,直到 prev 和 curr 指向的节点的值不相同时,我们将 prev 的 next 指针指向 curr,然后将 prev 和 curr 分别向后移动一个节点,重复以上过程直到遍历完整个链表。

代码如下:

def delete_duplicates(head):
    if not head:
        return head
    prev = head
    curr = head.next
    while curr:  
        if curr.val == prev.val:  
        prev.next = curr.next  
        else:  
        prev = curr  
        curr = curr.next  
    return head

以上就是几个使用单向链表解决 LeetCode 经典题目的例子。在实际的开发过程中,单向链表常常用于构建各种数据结构,例如队列、栈、图等等。掌握单向链表的基本操作和常用算法,对于编写高效的程序和解决实际问题都非常有帮助。

总之,链表是一种非常有用的数据结构,它可以帮助我们实现许多算法和数据结构。希望这篇文章能够让你对链表有更深入的了解,并且也能带给你一些快乐!