【问题标题】:Recursion within a class in pythonpython中类内的递归
【发布时间】:2019-04-17 23:49:09
【问题描述】:

我知道这是一个非常基本的问题,我在网上找到了示例代码,但我无法弄清楚它为什么会起作用。

如果我们需要以预序方式遍历二叉树,其中一种方法(在此引用 http://interactivepython.org/courselib/static/pythonds/Trees/TreeTraversals.html)是使用类似的方法:

def preorder(self):
     print(self.key)
     if self.leftChild:
        self.leftChild.preorder()
     if self.rightChild:
        self.rightChild.preorder()

我不明白为什么可以做类似self.leftChild.preorder() 的事情。如果有人能指出我正确的方向,我将不胜感激。

编辑:

按照 cmets 中的建议,我发布了我正在处理的完整代码。我是从 firecode 那里得到的:

post_ordered_list = []
class BinaryTree:

    def __init__(self, root_data):
        self.data = root_data
        self.left_child = None
        self.right_child = None

    def postorder(self):
        if self.left_child:
            self.left_child.postorder()
        if self.right_child:
            self.right_child.postorder()
        post_ordered_list.append(self.data)
        return post_ordered_list


    def get_right_child(self):
        return self.right_child

    def get_left_child(self):
        return self.left_child

    def set_root_val(self, obj):
        self.data = obj

    def get_root_val(self):
        return self.data

【问题讨论】:

  • 您可能需要在问题中包含类定义,以便我们理解其中一种方法
  • 添加了全班
  • 你不明白的地方能不能具体点?为什么self.leftChild.preorder() 不可能?
  • 是的。为什么self.leftChild.preorder() 有效? LeftChild 是一个实例属性,对吗?它如何连接到预购方法?
  • 代码中未显示但几乎可以肯定的是 self.leftChildself.rightChild 本身就是 BinaryTree 的实例,因此它们也有一个 preorder() 方法。

标签: python class oop data-structures


【解决方案1】:

在 Python 中,classInstance.methodName(params...) 等价于调用某个类的 methodName 方法,而 classInstance 传递为 self

  1. 确定哪个类是classInstance,可以通过type(classInstance)classInstance.__class__ 获得(查找classInstance = Class(...) 实例化代码)。
  2. 确定类是否以self 作为第一个参数定义methodNameself 是第一个参数表示它是一个实例方法,而不是类方法或静态方法(参见下面的链接解释)。如果有这样的方法,那么用selfclassInstance 来调用它,即等效调用是Class.methodName(classInstance, params...)
  3. 如果该类未定义methodName,则按照inheritance 的文档查找最接近的祖先,并执行类似操作。在这种情况下,等效调用如下所示:ParentClass.methodName(classInstnace, params...)

这是一个例子:

n4 = BinaryTree(4)
n5 = BinaryTree(5)
n3 = BinaryTree(3)
n3.left_child = n5
n3.right_child = n4
"""
  3
 /  \
4    5
"""

n3.postorder()
# result [5, 4, 3]

拨打n3.postorder()

  • selfn3
  • self.leftchildn5
  • self.rightchildn4

n3.postorder()被执行时,下一行是self.left_child.postorder(),并以self.leftchild(即n5)作为postorder的第一个参数(即“self”)调用postorder方法。因此,在这个递归调用的上下文中:

  • selfn5
  • self.leftchildNone
  • self.rightchildNone

然后该方法调用完成,下一行 self.right_child.postorder() 执行,这与上面的相同,但使用 n4,因为我们回到原始上下文中,其中 self.right_childn4

详细文档可在以下位置获得:https://docs.python.org/3/tutorial/classes.html

实例、类和静态方法的区别: https://julien.danjou.info/guide-python-static-class-abstract-methods/

【讨论】:

  • 在第一句话中,你classInstance.methodName(params...),这是有道理的。但是,left_child 不是类实例,对吗?那么,我为什么要self.left_child.postorder()?如果是这样的BinaryTree.postorder(self.left_child)),那我就明白了(这就是我解决问题的方法,但是看着解决方案和发布的答案,似乎每个人都在使用另一种方法)
  • 更新了答案以包含继承。 left_child 一个类实例。你可以使用self.left_child.postorder(),因为self.left_childn5,而n5BinaryTree 类型,因为n5 是使用n5 = BinaryTree(5) 定义的。
【解决方案2】:

递归有两个步骤:基本情况和递归情况。基本案例停止递归,但根据我从您的帖子中了解到的情况,您正在寻找递归案例的工作原理。递归情况的关键是下一个递归小于调用它的那个。

您的预购函数将根作为其参数。第一次调用 preorder 时,您使用 self 作为参数,这意味着您正在获取整个树。如果根有左孩子,那么下一步就是考虑左子树。左子树的根是当前根的左孩子,所以它作为参数传入。只要有一个左孩子,你就继续向下递归。当没有左孩子时递归停止,代码继续到最后一个节点的右孩子 if 语句。

我可以继续,但并非没有混淆。 (希望我还没有感到困惑!)在二进制搜索中查看this page,这是一个非常相似的操作。

【讨论】:

  • 感谢您的解释。我理解递归部分。我的困惑在于 OOP 领域。据我了解, self.left_child 是实例属性。如果那是一个对象,比如说tree=BinaryTree(),那么我会明白我可以做类似tree.postorder() 的事情;我正在使用我的实例来访问其中一种方法,在本例中是 postorder()。但是,我使用的是实例属性(在我的理解中,它是一个变量,将获得一个将传递给它的值)并且我不明白如何使用实例属性来访问该类中的方法。
  • left_child 和 right_child 也被键入为 BinaryTree。与 Java 或 C++ 等其他语言相比,在 Python 中查看类型要困难一些。变量的类型取决于您如何使用它,因此正是您在 left_child 和 right_child 上调用 postorder() 来确定这些变量是 BinaryTree。
【解决方案3】:

在开始编程时,递归是很难掌握的。您必须意识到的一件事是,如果您正确设置了基本情况(例如,如果 self.left、if self.right 等),您可以使用您正在处理的函数,就好像它是已经在工作了。

让我们考虑一个用函数countNodes()计算树中所有节点的例子: 假设我们在树的某个地方有节点 x。当调用 x 来计算其子树的所有节点时,它是如何做到的? 还记得我说过我们必须假装函数countNodes() 存在并且它已经工作吗?好吧,让我们这样做。 X 需要计算自己,因为它是一个节点。因此,到目前为止计数为 1。在它自己数完之后,它必须计算左边的所有节点和右边的所有节点。因此,要计算从任何节点 x 开始的树中的节点,我们为左侧调用 countNodes(),为右侧调用 countNodes()。

所以代码应该是这样的:

def countNodes(self):
    count = 1
    if self.left: #Checking to see if we have a left subtree
        count += countNodes(self.left)
    if self.right: #Checking to see if we have a right subtree
        count += countNodes(self.right)
    return count

现在我们更好地理解了递归,让我们回到你的例子。我们想以预排序的方式遍历树。这意味着,根据定义,我们必须首先访问我们的节点。然后我们看看我们是否有一个左节点要访问。如果是这样,我们以预排序方式遍历左节点。如果我们有一个正确的节点,我们也会以预排序方式遍历整个树。

递归的最大收获:

  1. 确保您有良好的基本案例(例如,如果 node.right == null)

  2. 找到重复的模式。只要您有正常工作的基本案例,您就可以在函数中使用该函数,就好像它已经构建了一样。

希望对你有帮助!

编辑:

解释我未能回答的自我部分。 Self 表示调用该方法的任何对象。因此,如果我们有一个节点 x 想要进行前序遍历,我们将使用x.preorder()。然后在函数中,它看起来像这样:

def preorder(self): #self is whatever calls this function
    print(self.value) #whaver you want to print
    if self.left:
       self.left.preorder() #now in this function call, self will be self.left
   if self.right:
       self.right.preorder() #same idea here


root.preorder() #calling the function. Now self will be root.

我希望这可以为您解决一些问题。很抱歉一开始没有理解这个问题。我希望这能回答它。如果您有更多问题或需要更多说明,请告诉我。

【讨论】:

  • 感谢您的回答。我理解递归部分。我不完全理解 OOP 部分。为什么我可以做类似 self.left_child.postorder() 的事情?我无法使用实例属性来解决问题,在我的理解中它是可变的(我可以传递任何我想要的整数并将其分配给 left_child)来访问方法。在我的理解中,那些. 意味着我访问了一个类中的一个方法。所以,如果是 tree= BinaryTree() 然后是 tree.post_order(),没问题。
  • 哦,好吧。我知道现在的困惑在哪里。基本上,python 起初对 self 是什么感到困惑。如果我们有一个名为 x 的节点,我们想以预序方式遍历,我们说 x.preorder()。 x 是对象,我们说我们要在节点 x 上调用 preorder。 Self 基本上是调用该方法的对象。在这种情况下,它将是 x。所以self.left指的是x.left,self.right指的是x.right。因为 x 调用了函数,所以 x 变成了 self。我希望这能消除一些困惑。
  • 那么,self.left_child 实际上创建了一个对象?
  • 不,它没有。它正在调用每个节点对象共享的成员变量。每个节点都有一个连接到它的左节点和一个右节点。也许这会有所帮助。 micropyramid.com/blog/understand-self-and-init-method-in-python-class/
猜你喜欢
  • 1970-01-01
  • 2019-05-06
  • 2021-07-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-03
相关资源
最近更新 更多