【问题标题】:Deleting node in BST Python在 BST Python 中删除节点
【发布时间】:2021-05-24 00:54:19
【问题描述】:

这是在 Python 中为 BST 实现添加和删除功能的一种可能方式。它有点类似于我对 C++ 中 BST 的想法。正如删除代码所表明的那样,我想删除一个节点,由于 Python 中缺少 C++ 中的引用传递,所以我不能这样做。除了del currNode(这不起作用)之外,什么是删除节点的好方法。我还有一个问题要澄清我关于在 Python 中通过引用传递的想法,当使用 add 函数添加节点时,当递归调用 add 时,它仍然“附加”到根节点。但是,当一个节点被删除时,它并没有从根节点“分离”。为什么会这样?

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

class bst(object):
    def __init__(self):
        self.root = None
    
    def add(self, value):
        
        def _add(self, currNode, value):
            if value < currNode.data:
                if currNode.left == None:
                    currNode.left = node(value)
                else:
                    _add(self, currNode.left, value)

            elif value > currNode.data:
                if currNode.right == None:
                    currNode.right = node(value)
                else:
                    _add(self, currNode.right, value)
        
            else:
                print("Duplicate found")

        
        if self.root == None:
            self.root = node(value)
        else:
            _add(self, self.root, value)


    def printBST(self):
        def _printBST(self, currNode):
            if currNode!= None:
                _printBST(self, currNode.left)
                print(currNode.data, end = " ")
                _printBST(self, currNode.right)
        
        if self.root != None:
            _printBST(self,self.root)
    

    def minBST(self,currNode):
        def _minBST(self, currNode):
            if currNode.left == None:
                return currNode.data
            else: return _minBST(self, currNode.left)
        
        if currNode != None:
            return _minBST(self, currNode)
        else:
            return -10000
    
    def deleteValue(self, val):
        
        def deleteNode(self, currNode, value):
            if currNode == None: 
                return
            elif value > currNode.data:
                return deleteNode(self, currNode.right, value)
            elif value < currNode.data:
                return deleteNode(self, currNode.left, value)
            else:
                if currNode.left == None and currNode.right == None:
                    #del currNode
                    currNode.data = None

                elif currNode.right == None:
                    currNode.data = None
                    #The address of currNode does not change
                    #as it happens in C++
                    #currNode = currNode.left

                elif currNode.left == None:
                    currNode.data = None
                    #The address of currNode does not change
                    #currNode = currNode.right
            
                else:
                    minV = self.minBST(currNode.right)
                    currNode.data = minV
                    return deleteNode(self, currNode.right, minV)

        deleteNode(self, self.root, val)
    


if __name__ == '__main__':
    b = bst()
    b.add(50)
    b.add(60)
    b.add(40)
    b.add(30)
    b.add(45)
    b.add(55)
    b.add(100)
    b.printBST()
    b.deleteValue(100)
    print("")
    b.printBST()
 

【问题讨论】:

    标签: python recursion binary-search-tree pass-by-reference


    【解决方案1】:

    您可以重新分配父节点并让垃圾收集器收集子节点,而不是使用del 删除节点。

    deleteNode 中返回新的子节点而不是删除节点。将返回的值分配给父级。

    def deleteNode(self, currNode, value):
        if not currNode: 
            return currNode
        elif value < currNode.data:
            return deleteNode(self, currNode.left, value)
        elif value > currNode.data:
            return deleteNode(self, currNode.right, value)
    
        else: 
            if not currNode.right:
                return currNode.left
    
            if not currNode.left:
                return currNode.right
    
            temp_val = currNode.right
            mini_val = temp_val.val
            while temp_val.left:
                temp_val = temp_val.left
                mini_val = temp_val.val
            currNode.right = deleteNode(currNode.right,currNode.val)
        return currNode
    

    【讨论】:

      【解决方案2】:

      节点结构和插入

      我们从一个简单的node 结构开始,但注意leftright 属性可以在构造时设置-

      # btree.py
      
      class node:
        def __init__(self, data, left=None, right=None):
          self.data = data
          self.left = left
          self.right = right
      

      递归是一种函数式遗产,因此将其与函数式风格一起使用会产生最佳效果。这意味着避免诸如突变、变量重新分配和其他副作用之类的事情。注意add 总是构造一个 new 节点而不是改变一个旧节点。这就是我们设计node 在构建时接受所有属性的原因 -

      # btree.py (continued)
      
      def add(t, q):
        if not t:
          return node(q)
        elif q < t.data:
          return node(t.data, add(t.left, q), t.right)
        elif q > t.data:
          return node(t.data, t.left, add(t.right, q))
        else:
          return node(q, t.left, t.right)
      

      中序遍历和字符串转换

      在我们add 一些节点之后,我们需要一种可视化树的方法。下面我们写一个inorder遍历和一个to_str函数-

      # btree.py (continued)
      
      def inorder(t):
        if not t: return
        yield from inorder(t.left)
        yield t.data
        yield from inorder(t.right)
      
      
      def to_str(t):
        return "->".join(map(str,inorder(t)))
      

      btree 对象接口

      请注意,我们并没有将上面的普通函数与类纠缠在一起,从而使它们过于复杂。我们现在可以定义一个 btree 面向对象的接口,它简单地包装了普通函数 -

      # btree.py (continued)
      
      class btree:
        def __init__(self, t=None): self.t = t
        def __str__(self): return to_str(self.t)
        def add(self, q): return btree(add(self.t, q))
        def inorder(self): return inorder(self.t)
      
      

      请注意,我们还将btree.py 写为它自己的模块。这定义了抽象的障碍,并允许我们扩展、修改和重用功能,而不会将它们与程序的其他区域纠缠在一起。让我们看看到目前为止我们的树是如何工作的 -

      # main.py
      
      from btree import btree
      
      t = btree().add(50).add(60).add(40).add(30).add(45).add(55).add(100)
      
      print(str(t))
      # 30->40->45->50->55->60->100
      

      最小值和最大值

      我们将继续这样工作,定义直接作用于node 对象的普通函数。接下来,minimummaximum -

      # btree.py (continued)
      
      from math import inf
      
      def minimum(t, r=inf):
        if not t:
          return r
        elif t.data < r:
          return min(minimum(t.left, t.data), minimum(t.right, t.data))
        else:
          return min(minimum(t.left, r), minimum(t.right, r))
      
      def maximum(t, r=-inf):
        if not t:
          return r
        elif t.data > r:
          return max(maximum(t.left, t.data), maximum(t.right, t.data))
        else:
          return max(maximum(t.left, r), maximum(t.right, r))
      

      btree 接口仅提供我们普通函数的包装 -

      # btree.py (continued)
      
      class btree:
        def __init__():       # ...
        def __str__():        # ...
        def add():            # ...
        def inorder():        # ...
        def maximum(self): return maximum(self.t)
        def minimum(self): return minimum(self.t)
      

      我们现在可以测试minimummaximum -

      # main.py
      
      from btree import btree
      
      t = btree().add(50).add(60).add(40).add(30).add(45).add(55).add(100)
      
      print(str(t))
      # 30->40->45->50->55->60->100
      
      print(t.minimum(), t.maximum())     # <-
      # 30 100
      

      从可迭代插入

      链接.add().add().add() 有点冗长。提供add_iter 函数允许我们从另一个可迭代对象中插入任意数量的值。我们现在引入它是因为我们也将在即将到来的remove 函数中重用它 -

      def add_iter(t, it):
        for q in it:
          t = add(t, q)
        return t
      
      #main.py
      
      from btree import btree
      
      t = btree().add_iter([50, 60, 40, 30, 45, 55, 100])   # <-
      
      print(str(t))
      # 30->40->45->50->55->60->100
      
      print(t.minimum(), t.maximum())
      # 30 100
      

      节点移除和前序遍历

      我们现在转到remove 函数。它的工作原理类似于add 函数,执行t.data 与要删除的值q 的比较。您会注意到我们在这里使用add_iter 来组合要删除的节点的leftright 分支。我们可以在此处为我们的树重用inorder 迭代器,但preorder 将使树更加平衡。这完全是一个不同的话题,所以我们现在不会讨论 -

      # btree.py (continued)
      
      def remove(t, q):
        if not t:
          return t
        elif q < t.data:
          return node(t.data, remove(t.left, q), t.right)
        elif q > t.data:
          return node(t.data, t.left, remove(t.right, q))
        else:
          return add_iter(t.left, preorder(t.right))
      
      def preorder(t):
        if not t: return
        yield t.data
        yield from preorder(t.left)
        yield from preorder(t.right)
      

      别忘了扩展btree接口-

      # btree.py (continued)
      
      class btree:
        def __init__():       # ...
        def __str__():        # ...
        def add():            # ...
        def inorder():        # ...
        def maximum():        # ...
        def minimum():        # ...
        def add_iter(self, it): return btree(add_iter(self.t, it))
        def remove(self, q): return btree(remove(self.t, q))
        def preorder(self): return preorder(self.t)
      

      现在让我们看看 remove 的实际效果 -

      # main.py
      
      from btree import btree
      
      t = btree().add_iter([50, 60, 40, 30, 45, 55, 100])
      
      print(str(t))
      # 30->40->45->50->55->60->100
      
      print(t.minimum(), t.maximum())
      # 30 100
      
      t = t.remove(30).remove(50).remove(100)      # <-
      
      print(str(t))
      # 40->45->55->60
      
      print(t.minimum(), t.maximum())
      # 40 60
      

      完成的 btree 模块

      这是我们在本回答过程中构建的完整模块 -

      from math import inf
      
      class node:
        def __init__(self, data, left=None, right=None):
          self.data = data
          self.left = left
          self.right = right
      
      def add(t, q):
        if not t:
          return node(q)
        elif q < t.data:
          return node(t.data, add(t.left, q), t.right)
        elif q > t.data:
          return node(t.data, t.left, add(t.right, q))
        else:
          return node(q, t.left, t.right)
      
      def add_iter(t, it):
        for q in it:
          t = add(t, q)
        return t
      
      def maximum(t, r=-inf):
        if not t:
          return r
        elif t.data > r:
          return max(maximum(t.left, t.data), maximum(t.right, t.data))
        else:
          return max(maximum(t.left, r), maximum(t.right, r))
      
      def minimum(t, r=inf):
        if not t:
          return r
        elif t.data < r:
          return min(minimum(t.left, t.data), minimum(t.right, t.data))
        else:
          return min(minimum(t.left, r), minimum(t.right, r))
      
      def inorder(t):
        if not t: return
        yield from inorder(t.left)
        yield t.data
        yield from inorder(t.right)
      
      def preorder(t):
        if not t: return
        yield t.data
        yield from preorder(t.left)
        yield from preorder(t.right)
      
      def remove(t, q):
        if not t:
          return t
        elif q < t.data:
          return node(t.data, remove(t.left, q), t.right)
        elif q > t.data:
          return node(t.data, t.left, remove(t.right, q))
        else:
          return add_iter(t.left, preorder(t.right))
      
      def to_str(t):
        return "->".join(map(str,inorder(t)))
      
      class btree:
        def __init__(self, t=None): self.t = t
        def __str__(self): return to_str(self.t)
        def add(self, q): return btree(add(self.t, q))
        def add_iter(self, it): return btree(add_iter(self.t, it))
        def maximum(self): return maximum(self.t)
        def minimum(self): return minimum(self.t)
        def inorder(self): return inorder(self.t)
        def preorder(self): return preorder(self.t)
        def remove(self, q): return btree(remove(self.t, q))
      

      把你的蛋糕也吃掉

      上述方法的一个低调的优点是我们为btree 模块提供了一个 接口。我们可以像演示的那样以传统的面向对象方式使用它,或者我们可以使用更实用的方法来使用它 -

      # main.py
      
      from btree import add_iter, remove, to_str, minimum, maximum
      
      t = add_iter(None, [50, 60, 40, 30, 45, 55, 100])
      
      print(to_str(t))
      # 30->40->45->50->55->60->100
      
      print(minimum(t), maximum(t))
      # 30 100
      
      t = remove(remove(remove(t, 30), 50), 100)
      
      print(to_str(t))
      # 40->45->55->60
      
      print(minimum(t), maximum(t))
      # 40 60
      

      补充阅读

      我已经写了大量关于此答案中使用的技术的文章。按照链接查看它们在其他上下文中的使用,并提供了额外的解释 -

      【讨论】:

      • 感谢您的回答。不过,我有疑问,产量如何负责从树中删除节点。我指的是 remove 函数的最后一个 else 子句,这里调用了 preorder。我最初的预感是,由于产量,存储在内存中的数据正在被返回并同时被删除。请纠正我。
      • @motiur,remove 函数的最后一个子句将删除节点 t,其中 t.data 等于 q,但该节点的分支 t.leftt.right 必须保留。为此,add_iter(t.left, preorder(t.right)) 实际上是merge 并创建一棵新树。 preorder(t.right) 用于将值一次插入t.left。这类似于我们在示例中创建树 t = add_iter(None, [50, 60, 40, 30, 45, 55, 100]) 的方式,但这会将数组 [50, 60, ...] 中的值插入到空树 None 中。这有意义吗?
      • 所以,如果我错了,请纠正我,所以当你在 remove 的最后一个子句中调用 add_iter 时,你正在合并左子树和右子树——但是节点发生了什么包含数据 - 它是否被销毁/删除,或者您只是忽略了它 - 并且没有返回它。
      • 这是与函数式技术的一个关键区别:不是操纵旧的(现有的)值,而是创建一个 新的 值。在之前的分支中,我们在树下重新创建了一个新的静脉,node(t.data, remove(t.left, q), t.right)node(t.data, t.left, remove(t.right, q))。这不是整个树的副本,而只是重新创建t.data == q路径。所以当t.data == q时,节点t被忽略并被垃圾回收,t.leftt.right合并形成新的子树。
      • 如果我们写t0 = add_iter(None, [1,2,3]),然后写print(t1),我会看到1-&gt;2-&gt;3。接下来我们定义t1 = remove(t0, 2)print(t1),我们将看到1-&gt;3。对remove 的调用确实不会改变t0,所以如果我们再次写print(t0),我们仍然会看到1-&gt;2-&gt;3。这就是持久的意思。如果您的程序的其他部分包含对t0 的引用,则树的完整性将始终保持完好。只有在清除了对节点的所有引用之后,节点才会真正被销毁。
      猜你喜欢
      • 2016-11-11
      • 2021-04-06
      • 1970-01-01
      • 2014-05-26
      • 1970-01-01
      • 1970-01-01
      • 2021-10-23
      相关资源
      最近更新 更多