Python 创建二叉树

Python 创建二叉树,python,class,if-statement,binary-tree,self,Python,Class,If Statement,Binary Tree,Self,我搜索的关于二叉树的大多数问题都显示了二叉搜索树的实现,而不是二叉树。完整二叉树的术语为: 一个空树或者它有一个节点和两个子节点,每个节点 child是另一个二叉树。 除最后一级外,所有级别都已满 最底层的所有叶子都是绿色的 尽量向左走。 我提出了一个概念,但它似乎没有正确地运行递归-有人知道我做错了什么吗 class Node(): def __init__(self, key): self.key = key self.left = None

我搜索的关于二叉树的大多数问题都显示了二叉搜索树的实现,而不是二叉树。完整二叉树的术语为:

一个空树或者它有一个节点和两个子节点,每个节点 child是另一个二叉树。 除最后一级外,所有级别都已满 最底层的所有叶子都是绿色的 尽量向左走。 我提出了一个概念,但它似乎没有正确地运行递归-有人知道我做错了什么吗

class Node():
    def __init__(self, key):
        self.key = key
        self.left = None
        self.right = None

    def add(self, key): 
        if self.key: 
            if self.left is None: 
                self.left = Node(key) 
            else: 
                self.left.add(key)

                if self.right is None: 
                    self.right = Node(key) 
                else: 
                    self.right.add(key) 
        else: 
            self.key = key
        return (self.key)

您可以使用sklearn决策树,因为它们也可以设置为二进制决策树。链接到文档。

您可以使用sklearn决策树,因为它们也可以设置为二进制决策树。链接到文档。

代码中的问题是多次添加相同的值。您添加了节点,然后仍然递归到更深的位置,在那里您也会这样做

更深层次的问题是,在到达树的底部级别之前,您不知道在哪里插入节点,并且已经检测到该级别不完整的地方。找到正确的插入点可能需要遍历整个树。。。这是击败了速度增益,你会期望从使用二叉树放在首位

我在此提供三种解决方案,首先是最有效的:

1.使用列表作为树实现 对于完整树,需要特别考虑:如果按级别对节点进行编号,从0开始为根,并且在每个级别内从左到右,则会注意到节点的父节点的编号为k-1/2,而其自身的编号为k。在另一个方向:如果编号为k的节点有子节点,则其左子节点的编号为k*2+1,右子节点的编号大于1

因为树是完整的,所以此编号中永远不会有间隙,因此可以将节点存储在列表中,并使用该列表的索引进行节点编号。现在将节点添加到树只意味着将其附加到该列表中。您只需要树列表,而不是节点对象,该列表中的索引是您的节点引用

下面是一个实现:

class CompleteTree(list):
    def add(self, key):
        self.append(key)
        return len(self) - 1

    def left(self, i):
        return i * 2 + 1 if i * 2 + 1 < len(self) else -1

    def right(self, i):
        return i * 2 + 2 if i * 2 + 2 < len(self) else -1            

    @staticmethod
    def parent(i):
        return (i - 1) // 2

    def swapwithparent(self, i):
        if i > 0:
            p = self.parent(i)
            self[p], self[i] = self[i], self[p]

    def inorder(self, i=0):
        left = self.left(i)
        right = self.right(i)
        if left >= 0:
            yield from self.inorder(left)
        yield i
        if right >= 0:
            yield from self.inorder(right)

    @staticmethod
    def depth(i):
        return (i + 1).bit_length() - 1
当然,这意味着您必须引用与使用实际节点类时略有不同的节点,但效率的提高是值得的

2.使用额外属性 如果您知道子树中有多少个节点,那么通过该数字的位表示,您可以知道下一个节点应该添加到哪里

例如,在示例树中有5个节点。想象一下,你想在那棵树上加一个6。根节点会告诉您当前有5个,因此需要将其更新为6。在二进制中是110。忽略最左边的1位,其余的位告诉您是向左还是向右。在这种情况下,您应该向右移动1,最后向左移动0,以该方向创建节点。您可以迭代或递归地执行此操作

下面是一个递归实现:

class Node():
    def __init__(self, key):
        self.key = key
        self.left = None
        self.right = None
        self.count = 1

    def add(self, key):
        self.count += 1
        if self.left is None:
            self.left = Node(key)
        elif self.right is None:
            self.right = Node(key)
        # extract from the count the second-most significant bit:
        elif self.count & (1 << (self.count.bit_length() - 2)):
            self.right.add(key)
        else:
            self.left.add(key)

    def inorder(self):
        if self.left:
            yield from self.left.inorder()
        yield self
        if self.right:
            yield from self.right.inorder()

tree = Node(1)
tree.add(2)
tree.add(3)
tree.add(4)
tree.add(5)
for node in tree.inorder():
    print(node.key)
这将从右向左递归搜索树,以查找要添加的节点的候选父节点


对于大型树,可以根据路径的长度在从根到叶的路径之间进行二元搜索,从而稍微改进这一点。但是它仍然没有前两种解决方案那么有效。

代码中的问题是,您多次添加相同的值。您添加了节点,然后仍然递归到更深的位置,在那里您也会这样做

更深层次的问题是,在到达树的底部级别之前,您不知道在哪里插入节点,并且已经检测到该级别不完整的地方。找到正确的插入点可能需要遍历整个树。。。这是击败了速度增益,你会期望从使用二叉树放在首位

我在此提供三种解决方案,首先是最有效的:

1.使用列表作为树实现 对于完整树,需要特别考虑:如果按级别对节点进行编号,从0开始为根,并且在每个级别内从左到右,则会注意到节点的父节点的编号为k-1/2,而其自身的编号为k。在另一个方向:如果编号为k的节点有子节点,则其左子节点的编号为k*2+1,右子节点的编号大于1

因为树是完整的,所以此编号中永远不会有间隙,因此可以将节点存储在列表中,并使用该列表的索引进行节点编号。现在将节点添加到树只意味着将其附加到该列表中。您只需要树列表,而不是节点对象,该列表中的索引是您的节点引用

下面是一个实现:

class CompleteTree(list):
    def add(self, key):
        self.append(key)
        return len(self) - 1

    def left(self, i):
        return i * 2 + 1 if i * 2 + 1 < len(self) else -1

    def right(self, i):
        return i * 2 + 2 if i * 2 + 2 < len(self) else -1            

    @staticmethod
    def parent(i):
        return (i - 1) // 2

    def swapwithparent(self, i):
        if i > 0:
            p = self.parent(i)
            self[p], self[i] = self[i], self[p]

    def inorder(self, i=0):
        left = self.left(i)
        right = self.right(i)
        if left >= 0:
            yield from self.inorder(left)
        yield i
        if right >= 0:
            yield from self.inorder(right)

    @staticmethod
    def depth(i):
        return (i + 1).bit_length() - 1
当然,这意味着您必须引用与使用实际节点类时略有不同的节点,但是 效率的提高是有回报的

2.使用额外属性 如果您知道子树中有多少个节点,那么通过该数字的位表示,您可以知道下一个节点应该添加到哪里

例如,在示例树中有5个节点。想象一下,你想在那棵树上加一个6。根节点会告诉您当前有5个,因此需要将其更新为6。在二进制中是110。忽略最左边的1位,其余的位告诉您是向左还是向右。在这种情况下,您应该向右移动1,最后向左移动0,以该方向创建节点。您可以迭代或递归地执行此操作

下面是一个递归实现:

class Node():
    def __init__(self, key):
        self.key = key
        self.left = None
        self.right = None
        self.count = 1

    def add(self, key):
        self.count += 1
        if self.left is None:
            self.left = Node(key)
        elif self.right is None:
            self.right = Node(key)
        # extract from the count the second-most significant bit:
        elif self.count & (1 << (self.count.bit_length() - 2)):
            self.right.add(key)
        else:
            self.left.add(key)

    def inorder(self):
        if self.left:
            yield from self.left.inorder()
        yield self
        if self.right:
            yield from self.right.inorder()

tree = Node(1)
tree.add(2)
tree.add(3)
tree.add(4)
tree.add(5)
for node in tree.inorder():
    print(node.key)
这将从右向左递归搜索树,以查找要添加的节点的候选父节点


对于大型树,可以根据路径的长度在从根到叶的路径之间进行二元搜索,从而稍微改进这一点。但是它仍然不如前两个解决方案那么有效。

您确实需要以某种方式扩充您的树。因为这不是一个二叉搜索树,所以关于每个节点的唯一真实信息是它是否有左、右子节点。不幸的是,这对浏览完整的二叉树没有帮助。想象一个完整的二叉树,有10层。在第9层之前,每个节点都有一个左子节点和一个右子节点,因此您无法知道要走哪条路径到达叶子。所以问题是,您向每个节点添加了哪些信息?我将在该树中添加节点数

保持计数是很容易的,因为每次你下降到一个子树上,你都知道要在该节点的计数中增加一个。您想要识别的是最左边的不完美子树。每个完美二叉树都有n=2^k-1,其中k是层次数,n是节点数。有一些简单快捷的方法可以检查一个数字是否小于2的幂1。请看第一个答案。事实上,在一个完整的二叉树中,每个节点最多有一个子节点,而这个子节点不是完美二叉树的根。按照简单规则添加节点:

如果左侧子级为None,则设置root.left=Nodekey并返回 否则,如果正确的子级为None,则设置root.right=Nodekey并返回 如果当前节点的一个子节点是不完美子树的根,则将该节点设为当前节点,并向下延伸到该子树 否则,如果大小不相等,则使具有较小子树的节点成为当前节点。 否则,将左侧子节点设为当前节点。
通过增加每个节点的根子树大小,您可以在每个节点上获得构建递归解决方案所需的所有信息。

您确实需要以某种方式增加树。因为这不是一个二叉搜索树,所以关于每个节点的唯一真实信息是它是否有左、右子节点。不幸的是,这对浏览完整的二叉树没有帮助。想象一个完整的二叉树,有10层。在第9层之前,每个节点都有一个左子节点和一个右子节点,因此您无法知道要走哪条路径到达叶子。所以问题是,您向每个节点添加了哪些信息?我将在该树中添加节点数

保持计数是很容易的,因为每次你下降到一个子树上,你都知道要在该节点的计数中增加一个。您想要识别的是最左边的不完美子树。每个完美二叉树都有n=2^k-1,其中k是层次数,n是节点数。有一些简单快捷的方法可以检查一个数字是否小于2的幂1。请看第一个答案。事实上,在一个完整的二叉树中,每个节点最多有一个子节点,而这个子节点不是完美二叉树的根。按照简单规则添加节点:

如果左侧子级为None,则设置root.left=Nodekey并返回 否则,如果正确的子级为None,则设置root.right=Nodekey并返回 如果当前节点的一个子节点是不完美子树的根,则将该节点设为当前节点,并向下延伸到该子树 否则,如果大小不相等,则使具有较小子树的节点成为当前节点。 否则,将左侧子节点设为当前节点。
通过增加每个节点的根子树大小,您可以在每个节点上获得构建递归解决方案所需的所有信息。

您可以发布一个获取返回树的代码示例吗?听起来您需要实现自平衡树之类的功能。也许从那里开始?TeeJay,我使用了这个问题的第一个答案中的代码来帮助我想象你的代码的一个问题是,在树的顶部变满后,你递归地向树的两条腿添加键。这是什么意思?难道我不想添加到树的左腿和右腿吗?您可以发布一个示例,说明您为获取返回的树而运行的代码吗?听起来您需要实现

就像一棵自我平衡的树。也许从那里开始?TeeJay,我使用了这个问题的第一个答案中的代码来帮助我想象你的代码的一个问题是,在树的顶部变满后,你递归地向树的两条腿添加键。这是什么意思?难道我不想添加到树的左腿和右腿吗?你知道有什么资源可以让我检查数字是否小于2的幂吗?我添加了一个链接,这只是一个巧妙的按位操作技巧。这是为了检查二的幂,所以要检查一的减,你只需做x&x+1==0而不是x&x-1==0,在弄清楚之后,你会如何下降到子树下?这个算法的第四步并不总是正确的。想象一下这棵树,按BFS顺序逐级列出:[1],[2,3],[4,5,null,null]。现在算法从1开始:它的两个子树都是一个完美子树的根,但是节点应该添加到右个子树下,而不是左子树下。啊,你说得很对。我曾试图避免使用位编码来生成更递归的解决方案,但现在它开始变得拥挤起来。我想说比最左边的小的应该能处理,谢谢!你知道有什么资源可以让我检查这个数字是否小于2的幂吗?我添加了一个链接,这只是一个巧妙的按位操作技巧。这是为了检查二的幂,所以要检查一的减,你只需做x&x+1==0而不是x&x-1==0,在弄清楚之后,你会如何下降到子树下?这个算法的第四步并不总是正确的。想象一下这棵树,按BFS顺序逐级列出:[1],[2,3],[4,5,null,null]。现在算法从1开始:它的两个子树都是一个完美子树的根,但是节点应该添加到右个子树下,而不是左子树下。啊,你说得很对。我曾试图避免使用位编码来生成更递归的解决方案,但现在它开始变得拥挤起来。我想说比最左边的小的应该能处理,谢谢!不幸的是我不能使用任何列表为什么不能?如果您有限制,请在您的问题中提及这些限制。比如:节点类是固定的吗?你能改一下吗?你必须使用这样一个类吗?你能使用字典、元组、集合、lambda、循环等等吗?无论如何,我在我的答案中添加了两个选项。我试图实现2,但在elif self.count&1行上出现了语法错误。实际上,这非常有效,我想这只是我在编写其余代码时做了一些更改。非常感谢你的帮助:不幸的是我不能使用任何列表为什么不能?如果您有限制,请在您的问题中提及这些限制。比如:节点类是固定的吗?你能改一下吗?你必须使用这样一个类吗?你能使用字典、元组、集合、lambda、循环等等吗?无论如何,我在我的答案中添加了两个选项。我试图实现2,但在elif self.count&1行上出现了语法错误。实际上,这非常有效,我想这只是我在编写其余代码时做了一些更改。非常感谢您的帮助: