Я знаю, что это очень простой вопрос, и я нашел примеры кодов в Интернете, но не могу понять, почему это работает.
Если нам нужно пройти по бинарному дереву в режиме предварительного порядка, один из способов сделать это (указан здесь http://interactivepython.org/courselib/static/pythonds/Tree/TreeTraversals.html) — использовать что-то вроде:
def preorder(self):
print(self.key)
if self.leftChild:
self.leftChild.preorder()
if self.rightChild:
self.rightChild.preorder()
Я не понимаю, почему можно делать что-то вроде self.leftChild.preorder(). Если кто-то может указать мне в правильном направлении, я буду очень признателен.
Обновлено:
Как было предложено в комментариях, я публикую полный код, над которым работаю. Я получил это от 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.leftChild и self.rightChild сами являются экземплярами BinaryTree, поэтому у них также есть метод preorder().
@MarkMeyer, ты хочешь сказать, что вне класса есть что-то вроде left_child=BinaryTree(data)?
@EminMammadov да, код не показывает, как добавляются дочерние узлы, но в большинстве реализаций они также будут деревьями. Это не обязательно должно быть за пределами класса, но оно не отображается в классе в вопросе.






Рекурсия — это то, что трудно понять, когда вы только начинаете программировать. Одна из вещей, которую вы должны понять, заключается в том, что если вы правильно настроили базовые случаи (например, если self.left, если 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
Итак, теперь, когда мы лучше понимаем рекурсию, давайте вернемся к вашему примеру. Мы хотим пройти по дереву в режиме предварительного заказа. Так что это по определению означает, что мы должны сначала посетить наш узел. Затем мы смотрим, есть ли у нас левый узел для посещения. Если это так, мы обходим левые узлы в порядке предварительного порядка. Если у нас есть правильная вершина, мы также обходим все дерево в предварительном порядке.
Основные выводы из рекурсии:
Убедитесь, что у вас есть хорошие базовые случаи (например, если node.right == null)
Найдите закономерность повторения. Вы можете использовать функцию в функции, как если бы она уже была построена, если у вас есть правильно работающие базовые случаи.
Надеюсь, это поможет!
Редактировать:
Чтобы объяснить ту часть себя, на которую я не ответил. 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.
Я надеюсь, что это проясняет некоторые вещи для вас. Извините, что сначала не понял проблемы. Я надеюсь, что это отвечает на него. Дайте мне знать, если у вас есть дополнительные вопросы или вам нужны дополнительные разъяснения.
Спасибо за ответ. Я понимаю часть рекурсии. Я не совсем понимаю часть ООП. Почему я могу сделать что-то вроде self.left_child.postorder()? Я не могу понять, используя атрибут экземпляра, который, как я понимаю, является переменной (я могу передать любое целое число, которое захочу, и назначить его для left_child) для доступа к методу. В моем понимании эти . означают, что я обращаюсь к методу внутри класса. Итак, если это будет что-то вроде tree= BinaryTree(), а затем tree.post_order(), нет проблем.
Ох, ну ладно. Теперь я вижу, где неразбериха. По сути, python поначалу сбивает с толку то, что такое self. Если у нас есть узел с именем x, который мы хотим обойти в режиме предварительного заказа, мы говорим x.preorder(). x — это объект, и мы говорим, что хотим вызвать предварительный порядок на узле x. Self — это в основном объект, вызвавший метод. В данном случае это будет х. Итак, self.left относится к x.left, self.right относится к x.right. Поскольку x вызывает функцию, x становится self. Я надеюсь, что это проясняет некоторую путаницу.
Итак, self.left_child действительно создает объект?
Нет, это не так. Он вызывает переменную-член, которую разделяет каждый объект узла. У каждого узла есть левый и правый узлы, которые к нему присоединены. Может быть, это может помочь. micropyramid.com/blog/understand-self-and-в этом-метод-в-pytхон-класс/
Рекурсия состоит из двух шагов: базового случая и рекурсивного случая. Базовый случай останавливает рекурсию, но, насколько я понимаю из вашего поста, вы ищете, почему работает рекурсивный случай. Ключом к рекурсивному случаю является то, что следующая рекурсия меньше, чем та, которая ее вызвала.
Ваша функция предварительного заказа принимает корень в качестве параметра. В первый раз, когда вы вызываете предварительный заказ, вы используете self в качестве параметра, что означает, что вы берете все дерево. Если у корня есть левый дочерний элемент, то следующим шагом будет рассмотрение левого поддерева. Корень левого поддерева является левым дочерним элементом текущего корня, поэтому он передается в качестве параметра. Вы продолжаете рекурсию вниз по левой стороне, пока есть левый дочерний элемент. Рекурсия останавливается, когда нет левого дочернего узла, и код продолжается до правого дочернего оператора if для этого последнего узла.
Я мог бы продолжить, но не без путаницы. (Надеюсь, я еще не запутался!) Проверьте эта страница в бинарном поиске, это очень похожее действие.
Спасибо за ваше объяснение. Я понимаю часть рекурсии. Моя путаница лежит в области ООП. Насколько я понимаю, self.left_child — это атрибут экземпляра. Если бы это был объект скажем tree=BinaryTree(), то я бы понял, что могу сделать что-то вроде tree.postorder(); Я использую свой экземпляр для доступа к одному из методов, в данном случае postorder(). Однако я использую атрибут экземпляра (в моем понимании это переменная, которая получит значение, которое будет передано ей), и я не понимаю, как я могу использовать атрибут экземпляра для доступа к методу внутри этого класса.
left_child и right_child также имеют тип BinaryTree. Видеть типы в Python немного сложнее, чем в некоторых других языках, таких как Java или C++. Тип переменной определяется тем, как вы ее используете, поэтому именно то, что вы вызываете postorder() для left_child и right_child, определяет, что эти переменные являются BinaryTree.
В Python classInstance.methodName(params...) эквивалентно вызову метода methodName некоторого класса, где classInstance передается как self:
classInstance, который вы можете получить с помощью type(classInstance) или classInstance.__class__ (ищите код создания экземпляра classInstance = Class(...)).methodName с self в качестве первого параметра. self будучи первым параметром, указывает, что это метод экземпляра, а не метод класса или статический метод (см. ниже объяснение ссылки). Если есть такой метод, то вызовите его с self равным classInstance, то есть эквивалентным вызовом будет Class.methodName(classInstance, params...).methodName, найдите ближайшего предка, который определяет, следуя документации для наследование, и сделайте то же самое. В этом случае эквивалентный вызов будет выглядеть так: 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()
self это n3self.leftchild это n5self.rightchild есть n4.Когда выполняется n3.postorder(), следующей строкой будет self.left_child.postorder(), и она вызывает метод postorder с self.leftchild (т. е. n5) в качестве первого параметра (т. е. «я») postorder. Следовательно, в контексте этого рекурсивного вызова:
self это n5self.leftchild это Noneself.rightchild есть None.Затем вызов этого метода завершается и выполняется следующая строка self.right_child.postorder(), и это то же самое, что и выше, но с n4, поскольку мы возвращаемся в исходный контекст, где self.right_child — это n4.
Подробная документация доступна по адресу: 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_child — это n5, а n5 имеет тип BinaryTree, поскольку n5 было определено с помощью n5 = BinaryTree(5).
Вам, вероятно, потребуется включить определение класса в свой вопрос, чтобы мы поняли смысл одного из его методов.