【问题标题】:Python recursion - how to exit earlyPython递归 - 如何提前退出
【发布时间】:2015-12-27 23:21:42
【问题描述】:

我一直在玩 BST(二叉搜索树),我想知道如何提前退出。以下是我为找到第 k 个最小而编写的代码。它递归地调用子节点的find_smallest_at_k,stack只是传递给函数的一个列表,用于按顺序添加所有元素。目前这个解决方案按顺序遍历所有节点,然后我必须从这个函数之外的“堆栈”中选择第 k 个项目。

def find_smallest_at_k(self, k, stack, i):
    if self is None:
        return i

    if (self.left is not None): 
        i = self.left.find_smallest_at_k(k, stack, i) 

    print(stack, i)
    stack.insert(i, self.data)
    i += 1
    if i == k: 
        print(stack[k - 1]) 
        print "Returning" 

    if (self.right is not None): 
        i = self.right.find_smallest_at_k(k, stack, i) 

    return i 

是这样称呼的,

our_stack = []
self.root.find_smallest_at_k(k, our_stack, 0) 
return our_stack[k-1] 

我不确定是否可以提前退出该功能。如果我的 k 是 1,我真的不必遍历所有节点然后找到第一个元素。从外部函数传递列表也感觉不对 - 感觉就像在 C 中传递指向函数的指针。有人能提出比我迄今为止所做的更好的替代方案吗?

【问题讨论】:

  • 我不是 100% 确定你在问什么,但我认为你的问题是“在二叉搜索树中找到第 k 个(有序)项的有效/优雅方法是什么。如果是这样,那么我建议你创建一个遍历树的迭代器,yield按顺序排列值,然后有一个函数 get_kth 只在迭代器上循环,从中返回第 k 个结果。
  • @TomDalton - 问题主要是看看我是否可以巧妙地安排递归的基本情况以返回第 k 个最小的情况而不遍历所有节点。我同意更好的方法是使用迭代而不是递归,但是坚持使用递归是否有更有效的方法来做到这一点?我更多是出于好奇而问了这个问题。如果不抛出 Exception 可能会展开所有堆栈帧,我想不出一种摆脱递归的方法。

标签: python recursion binary-search-tree exit


【解决方案1】:

将列表作为参数传递:将列表作为参数传递可能是一种很好的做法,如果您将函数设为尾递归。否则毫无意义。在 BST 中,有两个潜在的递归函数调用需要完成,这个问题有点高。

否则,您可以返回列表。我看不出变量i 的必要性。无论如何,如果你绝对需要返回多个值,你总是可以使用像 return i, stacki, stack = root.find_smallest_at_k(k) 这样的元组。

快速转发:对于快速转发,注意 BST 父节点的右节点总是大于父节点。因此,如果你总是在正确的孩子上下降树,你最终会得到一个不断增长的值序列。因此,该序列的第一个 k 值必然是最小的,因此在一个序列中向右 k 次或更多次是没有意义的。

即使在你下降的中间,你有时也会向左走,在右边多走k 次是没有意义的。 BST 属性确保如果您正确,则层次结构中的所有后续数字都将大于父级。因此,向右k 次或更多是没有用的。

代码:这是一个快速制作的伪python代码。它没有经过测试。

def findKSmallest( self, k, rightSteps=0 ):
    if rightSteps >= k: #We went right more than k times
        return []
    leftSmallest = self.left.findKSmallest( k, rightSteps ) if self.left != None else []
    rightSmallest = self.right.findKSmallest( k, rightSteps + 1 ) if self.right != None else []
    mySmallest = sorted( leftSmallest + [self.data] + rightSmallest )
    return mySmallest[:k]

根据我的评论编辑另一个版本。

def findKSmallest( self, k ):
    if k == 0:
        return []
    leftSmallest = self.left.findKSmallest( k ) if self.left != None else []
    rightSmallest = self.right.findKSmallest( k - 1 ) if self.right != None else []
    mySmallest = sorted( leftSmallest + [self.data] + rightSmallest )
    return mySmallest[:k]

注意,如果k==1,这确实是最小元素的搜索。任何向右移动,都将立即返回[],这无济于事。

【讨论】:

  • 我稍微考虑了这一点,并且在优化方面,如果你让i 向右移动,你知道你已经遇到了至少i 值,这些值将小于你会发现的任何值在当前节点下方。因此,您可以安全地只返回(k-i) 最小的元素。在我的伪 python 代码中,这可以用return mySmallest[:k-rightSteps] 完成,但也可以完全摆脱参数rightSteps 并执行以下递归调用self.right.findKSmallest( k - 1 )。如果k == 0,总是返回[]
【解决方案2】:

正如 Lærne 所说,您必须关心将函数转换为尾递归函数;那么您可能会对使用延续传递样式感兴趣。因此,您的函数可以调用自身或“转义”函数。我编写了一个名为tco 的模块来优化尾调用;见https://github.com/baruchel/tco 希望对您有所帮助。

【讨论】:

    【解决方案3】:

    这是另一种方法:它不会提前退出递归,而是在不需要时阻止额外的函数调用,这基本上是您想要实现的目标。

    class Node:
        def __init__(self, v):
            self.v = v
            self.left = None
            self.right = None
    
    def find_smallest_at_k(root, k):
        res = [None]
        count = [k]
    
        def helper(root):
            if root is None:
                return
    
            helper(root.left)
            count[0] -= 1
            if count[0] == 0:
                print("found it!")
                res[0] = root
                return
    
            if count[0] > 0:
                print("visiting right")
                find(root.right)
    
        helper(root)
        return res[0].v
    

    【讨论】:

      【解决方案4】:

      如果您想尽快退出,请使用 exit(0)。 这将使您的任务变得轻松!

      【讨论】:

      • 当 OP 说退出时,OP 并不意味着退出程序,而是提前停止对于路径无助于返回正确结果的路径的递归。
      猜你喜欢
      • 1970-01-01
      • 2012-11-09
      • 2019-10-06
      • 2020-01-14
      • 2020-01-06
      • 2021-11-18
      • 2019-07-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多