【问题标题】:Return average value of node in binary tree python recursively递归返回二叉树python中节点的平均值
【发布时间】:2017-07-25 16:54:38
【问题描述】:

假设我有这样一个二叉树:

我想创建一个返回树平均值的函数,在本例中为(5+3+2+6)/(4) = 4

我做我预购遍历,这里是我的功能:

def helper(root, total=0, amount=0):
    if root != None:
        total += root.data
        amount += 1
        helper(root.left, total, amount)
        helper(root.right, total, amount)      

    return (root, total, amount)

def avg(root):
    av = helper(root)
    return av[1]/av[2]

但是,此代码仅返回 (Node 5, total = 5, amount = 1)。就好像它只扫描first 节点,我不知道我上面的代码为什么或有什么问题。

class btn(object):

    def __init__(self, data):
        self.data = data
        self.left = None
        self.right = None

【问题讨论】:

    标签: python recursion binary-tree binary-search-tree


    【解决方案1】:

    调用helper 时没有使用返回值。这些应该用于确定整个树中的值的总和:

    def helper(root, total=0, amount=0):
        if root != None:
            total += root.data
            amount += 1
            _, left_total, left_amount = helper(root.left, total, amount)
            _, right_total, right_amount = helper(root.right, total, amount)
            total += left_total
            total += right_total
            amount += left_amount
            amount += right_amount
    
        return (root, total, amount)
    

    以下行将helper 的返回值“解包”为三个值中的每一个:

    _, left_total, left_amount = helper(root.left, total, amount)
    

    三个值中的第一个被分配给_,但它被忽略了(变量_通常在这种情况下使用),然后是left_totalleft_amount,它们是第二个和第三个值在返回的元组中。

    【讨论】:

    • _, 是什么意思?
    • 我明白你的意思,但请解释一下上面的语法?
    • @VeeshaDawg 我已经扩展了答案。
    • 你为什么不把它们存储为root
    • @VeeshaDawg 该值没有被使用。我们甚至可以更新代码以完全不返回 root 节点作为第一个值(除非有理由保留该代码)。
    【解决方案2】:
    def helper(root, total=0, amount=0):
    if root != None:
        total += root.data
        amount += 1
        (_, total, amount) = helper(root.left, total, amount)
        (_, total, amount) = helper(root.right, total, amount)
    return (root, total, amount)
    

    您将当前的总数和金额提供给辅助函数,但您没有存储它们返回的新值。

    【讨论】:

      【解决方案3】:

      检测到反模式

      您已经在对状态变量使用递归 - 无需使用重新分配来增加复杂性。此页面上的其他答案因犯了同样的错误而使您失望

      def avg (tree):
        def helper (node, sum, count):
          if node is None:
            return (0, 0)
          else:
            (Lsum, Lcount) = helper(node.left, 0, 0)
            (Rsum, Rcount) = helper(node.right, 0, 0)
            return (node.data + Lsum + Rsum, 1 + Lcount + Rcount)
        (sum, count) = helper(tree, 0, 0)
        return sum/count if count > 0 else None
      
      # your node class
      class Node (object):
        def __init__(self, data, left, right):
          self.data = data
          self.left = left
          self.right = right
      
      # make your tree
      tree = Node(5, Node(3, Node(2, None, None), None), Node(6, None, None))
      
      print(avg(tree)) #=> 4.0
      
      # ensure that this works for an empty tree too (it does)
      print(avg(None)) #=> None
      

      直觉

      递归使我们能够对此产生非常好的直觉——特别是粗体线

      (Lsum, Lcount) = helper(node.left, 0, 0)
      (Rsum, Rcount) = helper(node.right, 0, 0)
      return (node.data + Lsum + Rsum, 1 + Lcount + Rcount)

      return 是说返回一个 (sum, count) 的元组

      • 对于 sum,此节点的数据加上左右和的任何值
      • 对于 count,计算这个节点 (1) 加上左右计数是什么

      这样写,我们可以很清楚地看到我们的函数必须处理的两种情况:

      1. 当节点为None时,我们将(0, 0)贡献给最终计算
      2. 否则,我们将节点的data 贡献给sum,并将1 贡献给count

      内联代码说明

      # we only need to expose one function
      def avg (tree):
      
        # helper isn't exposed outside of avg
        # helper has stateful parameters
        def helper (node, sum, count):
      
          # helper is a recursive function, start with the base case of empty Node
          if node is None:
            # our base sum and count are 0
            return (0, 0)
      
          # process the Node
          else:
      
            # get L sum and count the same way we initialized helper with the tree
            (Lsum, Lcount) = helper(node.left, 0, 0)
      
            # do the same for the R side
            (Rsum, Rcount) = helper(node.right, 0, 0)
      
            # no reassignment of sum or count is necessary,
            # simply recurse using the new state values of each
            return (
              node.data + Lsum + Rsum, # sum equals this node plus L and R sums
              1 + Lcount + Rcount      # count equals 1 plus L and R counts
            )
      
        # always init the sum and count with 0
        (sum, count) = helper(tree, 0, 0)
      
        # don't divide by zero if the tree is empty; instead return None
        return sum/count if count > 0 else None
      

      清理所有中间值

      让我们看看这个

      (Lsum, Lcount) = helper(node.left, 0, 0)
      (Rsum, Rcount) = helper(node.right, 0, 0)
      return (node.data + Lsum + Rsum, 1 + Lcount + Rcount)
      

      如果您像我一样,尽管事实上这是使用重新分配对其他答案的巨大改进,但仍然使用 4 个中间值。如果我们能稍微清理一下就好了——而且我们可以。

      如果我们有一个可以获取元组列表并将所有值添加到各自位置的函数会怎样?

      // if only ...
      sum_tuples( (0, 10), (1, 20), (2, 30) )
      # ... ( 0 + 1 + 2 , 10 + 20 + 30 )
      #=> (3, 60)
      

      zip 的帮助下,这个函数实际上很容易编写。这个函数是泛型的,所以在我们avg函数以外的地方都有用,所以我单独定义一下

      def sum_tuples (*xs):
        return tuple(sum(x) for x in zip(*xs))
      
      sum_tuples( (0,10), (1,20), (2,30) )
      #=> (3, 60)
      

      现在看看它对avg 的影响——不再有中间值(bold 的变化)

      def avg (tree):
        def helper (node, sum, count):
          if node is None:
            return (0, 0)
          else:
            return sum_tuples(
              (node.data, 1),
              helper(node.left, 0, 0),
              helper(node.right, 0, 0)
            )
        (sum, count) = helper(tree, 0, 0)
        return sum/count if count > 0 else None

      当然它的工作原理和以前一样,只是现在它已经尽可能漂亮了。

      【讨论】:

        猜你喜欢
        • 2020-02-21
        • 2015-09-02
        • 1970-01-01
        • 2023-04-07
        • 2023-03-29
        • 2012-01-26
        • 2015-08-31
        • 2020-09-15
        • 2018-05-03
        相关资源
        最近更新 更多