简体   繁体   English

递归返回链表的中间节点

[英]Return middle node of linked list with recursion

A Graph is a non-linear data structure consisting of nodes and edges.图是由节点和边组成的非线性数据结构。 The nodes are sometimes also referred to as vertices and the edges are lines or arcs that connect any two nodes in the graph.节点有时也称为顶点,边是连接图中任意两个节点的线或弧。 More formally a Graph can be defined as更正式地,图可以定义为

Recursion is a poor choice for operating on linked lists.对于链表操作,递归是一个糟糕的选择。 Almost always use a loop, which is simple to reason about, has less overhead and doesn't limit the size of the list to the call stack.几乎总是使用循环,这很容易推理,开销较小,并且不会将列表的大小限制为调用堆栈。 It's easier to access and manipulate surrounding elements iteratively.更容易迭代地访问和操作周围的元素。

Getting the midpoint of a linked list is easy iteratively: keep two references to the head, then move one twice as fast as the other until the fast reference reaches the end of the list.迭代获取链表的中点很容易:保持两个对头部的引用,然后以两倍于另一个的速度移动一个,直到快速引用到达链表的末尾。 The slow pointer will point to the middle node.慢速指针将指向中间节点。 The two-pointer technique is one of the fundamental tools for working with linked lists.两指针技术是处理链表的基本工具之一。

from collections import namedtuple

def middle_node(fast):
    slow = fast

    while fast and fast.next:
        fast = fast.next.next
        slow = slow.next

    return slow

if __name__ == "__main__":
    Node = namedtuple("Node", "val next")
    odd = Node(0, Node(1, Node(2, Node(3, Node(4, None)))))
    even = Node(0, Node(1, Node(2, Node(3, Node(4, Node(5, None))))))
    print(middle_node(odd).val) # => 2
    print(middle_node(even).val) # => 3

You can do this recursively using the exact same methodology: a fast and a slow reference.您可以使用完全相同的方法递归地执行此操作:快速和慢速参考。 Here's an example that plugs into the above boilerplate:这是一个插入上述样板的示例:

def middle_node(fast, slow=None):
    if not slow:
        slow = fast

    if fast and fast.next:
        return middle_node(fast.next.next, slow.next)

    return slow

For odd-length lists with two middle elements, these methods always return the one closer to the tail, but you can add an additional fast.next.next check to the base case or loop termination conditional to return the middle element closer to the head if you need.对于具有两个中间元素的奇数长度列表,这些方法总是返回更靠近尾部的列表,但您可以在基本情况或循环终止条件中添加额外的fast.next.next检查以返回更靠近头部的中间元素如果你需要。

You can see the recursive version offers no benefits in readability or elegance to justify the added overhead and size restrictions it imposes.您可以看到递归版本在可读性或优雅方面没有任何好处,以证明它所施加的额外开销和大小限制是合理的。 Recursion is better suited for nonlinear nested structures like trees where the data is wide (meaning the call stack is much more able to hold the data without overflowing).递归更适合非线性嵌套结构,例如数据很宽的树(这意味着调用堆栈更能保存数据而不会溢出)。 The nonlinear nature of trees makes it very awkward to use a loop and explicit stack to handle certain typical traversals.树的非线性特性使得使用循环和显式堆栈来处理某些典型的遍历非常尴尬。

The reason why this prints the right value but changing the print to a return statement doesn't work is because you don't return the node in your base case.这打印正确的值但将打印更改为 return 语句不起作用的原因是因为您没有在基本情况下返回节点。 So when you find the mid point and return the node, the previous node does not return anything or utilize the result of the recursive step.因此,当您找到中点并返回节点时,前一个节点不会返回任何内容或利用递归步骤的结果。 Here is a modification that will utilize the result from your recursive step and return it down the call chain.这是一个修改,它将利用递归步骤的结果并将其返回调用链。

I'm not entirely convinced you midpoint calculation was correct in every case (case of 3 nodes will return node 1 in stead of node 2) so I have modified it slightly.我并不完全相信您的中点计算在每种情况下都是正确的(3 个节点的情况将返回节点 1 而不是节点 2)所以我稍微修改了它。

def find_mid(self, node, ret_count, ):
    if node.next == None:
        self.len += 1
        return None
    else:
        self.len += 1
        # return_node will either be the midpoint if we have found it, or None if we are still searching
        return_node = self.find_mid(node.next, ret_count + 1)

        # We have found the midpoint no need to check ret_count anymore
        if return_node:
            return return_node

    # If we make it here we have not found the midpoint node but have counted the total number of Nodes.
    # Set midpoint assuming an even number of nodes
    midpoint = int(self.len/2)
    # If odd number of nodes set midpoint accordingly
    if self.len % 2 != 0:
        midpoint = int((self.len+1)/2)

    # Check if the current node is the midpoint (len = 3 or len = 4 results in node 2 being midpoint
    if ret_count == midpoint:
        # Return the node
        return node
    else:
        # Potentially not necessary but will ensure that non-midpoint recursive steps return None
        return None

Recursion is a great choice for processing linked lists because linked lists are recursive data structures.递归是处理链表的绝佳选择,因为链表是递归数据结构。 Recursion allows our program's structure to match that of our data's structure -递归允许我们的程序结构与我们的数据结构相匹配——

# linked_list.py

empty = None

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

def to_str(t = empty):
  if not t:
    return "None"
  else:
    return f"{t.value} -> {to_str(t.next)}"

def middle(t = empty):
  def loop(t, ff):
    if not t:
      return None
    elif ff and ff.next:
      return loop(t.next, ff.next.next)
    else:
      return t.value
  return loop(t, t)

Let's get some output from our basic building blocks -让我们从我们的基本构建块中获取一些 output -

# main.py

from linked_list import node, to_str, middle

t1 = node(1, node(2, node(3)))
t2 = node(1, node(2, node(3, node(4))))
t3 = node(1, node(2, node(3, node(4, node(5)))))

print(to_str(t1)) # 1 -> 2 -> 3 -> None
print(to_str(t2)) # 1 -> 2 -> 3 -> 4 -> None
print(to_str(t3)) # 1 -> 2 -> 3 -> 4 -> 5 -> None

print(middle(t1)) # => 2
print(middle(t2)) # => 3
print(middle(t3)) # => 3

Wrapping up the functional module in a class makes it feel more pythonic -将功能模块包裹在class中让它感觉更pythonic -

# linked_list.py

empty = # ...

class node # ...

def to_str # ...

def middle # ...

def from_list(l = []):
  if not l:
    return empty
  else:
    return node(l[0], from_list(l[1:]))

class linked_list:
  def __init__(self, root = None):
    self.root = root
  def __str__(self):
    return to_str(self.root)
  def middle(self):
    return middle(self.root)
  def from_list(l = []):
    return linked_list(from_list(l))

Now we get all the benefits of a functional-style module with the conveniences of an oop-style interface -现在我们获得了函数式模块的所有好处和 oop 式接口的便利——

from linked_list import linked_list

t1 = linked_list.from_list([1, 2, 3])
t2 = linked_list.from_list([1, 2, 3, 4])
t3 = linked_list.from_list([1, 2, 3, 4, 5])

print(t1) # 1 -> 2 -> 3 -> None
print(t2) # 1 -> 2 -> 3 -> 4 -> None
print(t3) # 1 -> 2 -> 3 -> 4 -> 5 -> None

print(t1.middle()) # => 2
print(t2.middle()) # => 3
print(t3.middle()) # => 3

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM