【问题标题】:How does this recursive function to reverse a linked list work?这个用于反转链表的递归函数是如何工作的?
【发布时间】:2021-07-27 16:17:39
【问题描述】:

我在下面找到了递归反转链表的函数:

def recursive(self, head, end):
    if not head:
        return None, None
    if head.next == end:
        head.next = None
        return head, head
    newHead, newEnd = self.recursive(head.next, end)
    newEnd.next = head
    head.next = None
    return newHead, head

我了解涵盖基本情况的 if 语句。

但我不明白递归关系。

该递归如何反转列表?是否有更简单的递归版本来反转链表?作为参考,我正在解决 LeetCode 问题206. Reverse Linked List

给定一个单链表的head,反转该列表,并返回反转后的列表

【问题讨论】:

    标签: python recursion linked-list


    【解决方案1】:

    我不明白递归关系。

    假设我们有这个链表:

     head                                                  end
      ↓                                                     ↓
    ┌───────────┐    ┌───────────┐                        ┌───────────┐
    │ value: 85 │    │ value: 15 │                        │ value: 20 │
    │ next: ———————→ │ next: ———————→ ...more nodes...——→ │ next:null │
    └───────────┘    └───────────┘                        └───────────┘ 
    

    递归部分基于以下观察:

    如果您可以反转一个短一个元素的列表,排除当前head节点,那么我们应该会遇到这样的情况:

     head                                                  end
      ↓                                                     ↓
    ┌───────────┐    ┌───────────┐                        ┌───────────┐
    │ value: 85 │    │ value: 15 │                        │ value: 20 │
    │ next: ———————→ │           │                        │           │
    │           │    │ next:null │ ←——...more nodes...←———————— :next │ 
    └───────────┘    └───────────┘                        └───────────┘ 
                       ↑                                    ↑
                      newEnd                               newHead
    

    在这个阶段,我们不会质疑它是如何做到的。我们只是假设它适用于递归情况。所以如果它工作正常,我们应该得到上面的列表状态。

    现在剩余的语句将链接当前的head 节点,以便它完成一个列表的反转工作,包括这个节点也:

    newEnd.next = head
    

    这会产生这种状态:

     head                                                  end
      ↓                                                     ↓
    ┌───────────┐    ┌───────────┐                        ┌───────────┐
    │ value: 85 │    │ value: 15 │                        │ value: 20 │
    │ next: ———————→ │           │                        │           │
    │           │ ←——————— :next │ ←——...more nodes...←———————— :next │ 
    └───────────┘    └───────────┘                        └───────────┘ 
                       ↑                                    ↑
                      newEnd                               newHead
    

    然后我们执行:

    head.next = None
    

    这两个赋值使当前head成为我们从递归返回的反向列表的尾节点:

     head                                                  end
      ↓                                                     ↓
    ┌───────────┐    ┌───────────┐                        ┌───────────┐
    │ value: 85 │    │ value: 15 │                        │ value: 20 │
    │ next:null │ ←——————— :next │ ←——...more nodes...←———————— :next │ 
    └───────────┘    └───────────┘                        └───────────┘ 
                       ↑                                    ↑
                      newEnd                               newHead
    

    现在我们只需要告诉调用者哪个是这个反向列表的头和尾节点:

    return newHead, head
    

    当您查看最终状态时,您确实会看到它们是反向列表的头部和尾部。

    所以,现在,我们知道了:

    • 基本案例有效(您已经很清楚了)
    • 递归案例在递归正确返回排除第一个节点的列表的反向列表的条件下起作用

    通过归纳,您可以看到,如果它适用于只有一个节点的列表,它也适用于具有 2、3、...等的列表。

    备注

    • 您链接到的 LeetCode 问题没有使用 end 引用,因此您不需要使用它们。
    • 递归有其局限性。当列表很长时,您可能会遇到堆栈溢出异常。

    一种迭代方法是在您沿着列表遍历并重新链接每个next 引用时保留对前一个节点的引用。以下是 LeetCode 挑战的工作原理(没有 end 参考):

    class Solution:
        def reverseList(self, head: ListNode) -> ListNode:
            prev = None
            while head:
                head.next, prev, head = prev, head, head.next
            return prev
    

    【讨论】:

      【解决方案2】:

      实际上,我不明白那个功能。首先,您应该只需要将列表的头部传递给这样的函数;我不知道为什么要传递参数 end 或者为什么甚至需要传递它。此外,参数 self 表明这是一个方法,它是未显示的类定义的一部分。让我们重新开始吧:

      这是一个简单的类,用于定义一个节点,该节点可以链接到另一个节点并用“名称”标记以进行识别。我们还包括一个小函数来遍历链表以打印出节点:

      class Node:
          def __init__(self, name):
              self.name = name
              self.next = None
      
      a = Node('A')
      b = Node('B')
      c = Node('C')
      d = Node('D')
      e = Node('E')
      a.next = b
      b.next = c
      c.next = d
      d.next = e
      
      def print_list(head):
          while head:
              print(head.name)
              head = head.next
      
      print_list(a)
      

      打印:

      A
      B
      C
      D
      E
      

      接下来我们介绍递归函数来反转这样的列表。此函数传递列表的头节点,断言不是None。其余的我试图在算法的每个步骤中解释为 cmets。关键点是评论 7:让我们一直指向的东西现在指向我们。这与注释 5 相关:我们现在向后指向无,因为当我们设置 head.next = None 时,如果此节点是整个列表的头节点,它将仅保持 None。否则,根据评论 7,它稍后将被更新为指向前一个节点。

      def reverse(head):
          assert(head) # 1. make sure head is not None
          next_head = head.next # 2. get the next node, if any
          if next_head is None: # 3. if we do not point to a next node, then
              return head # 4. just return this node as the new head
          head.next = None # 5. we now point backwards to None
          # 6. otherwise, reverse recursively what we were pointing to and get the new head
          new_head = reverse(next_head)
          # 7. make what we had been pointing to now point back to us:
          next_head.next = head
          # 8. and return the new head
          return new_head
      
      # without comments for clarity:
      def reverse_no_comments(head):
          assert(head)
          next_head = head.next
          if next_head is None:
              return head
          head.next = None
          new_head = reverse(next_head)
          next_head.next = head
          return new_head
      
      def print_list(head):
          while head:
              print(head.name)
              head = head.next
              
      
      print('Reversed:')
      new_head = reverse(a)
      print_list(new_head)
      

      打印:

      Reversed:
      E
      D
      C
      B
      A
      

      【讨论】:

        猜你喜欢
        • 2021-07-12
        • 2016-11-01
        • 2019-05-28
        • 1970-01-01
        • 2011-03-29
        • 1970-01-01
        • 1970-01-01
        • 2018-07-11
        • 2019-08-13
        相关资源
        最近更新 更多