deleting a node from a binary search tree using recursion

点点圈 提交于 2020-06-17 09:10:02

问题


So I'm trying to delete a node from a tree by using these two functions inside the class.Unfortunately it just doesnt delete anything and i was wondering what is wrong about it! any help would be truly appreciated.

def Find_Min(self,node):
        current=node
        while current.left is None:
             current=current.left
        return current



    def deletenode(self,node,ntbd):  ##ntbd:node to be deleted  /// node: root node
        if node is None:
            return None
        elif node.data>ntbd:
            node.left=self.deletenode(node.left,ntbd)
        elif node.data<ntbd:
            node.right=self.deletenode(node.right,ntbd)
        else:  ##Found you bastard
            if node.left==None and node.right==None:
                node=None
            elif node.left==None:
                temp=node.right
                node=None
                print("----",temp)
            elif node.right==None:
                temp=node.left
                node=None
                print("----",temp)
            else:
                smallest=self.Find_Min(node.right)
                node.data=smallest.data
                node.right=self.deletenode(node.right,smallest.data)

回答1:


Given node -

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

Let's create a tree t -

t = node \
  ( 1
  , node(2, node(3), node(4))
  , node(5, node(6), node(7))
  )

Which represents this tree -

       1
      / \
     /   \
    2     5
   / \   / \
  3   4 6   7

plain functions

First a way to print trees, to_str -

def to_str (root = None):
  if not root:
    return "_"
  else:
    return f"(node {root.data} {to_str(root.left)} {to_str(root.right)})"

print(to_str(t))
# (node 1 (node 2 (node 3 _ _) (node 4 _ _)) (node 5 (node 6 _ _) (node 7 _ _)))

Now a way to delete nodes -

def delete (root = None, q = None):
  if not root or root.data == q:
    return None
  else:
    return node(root.data, delete(root.left, q), delete(root.right, q))

print(to_str(t))
# (node 1 (node 2 (node 3 _ _) (node 4 _ _)) (node 5 (node 6 _ _) (node 7 _ _)))

print(to_str(delete(t, 2)))
# (node 1 _ (node 5 (node 6 _ _) (node 7 _ _)))

Notice the similarity between the two programs. And notice delete returns a new tree and does not destroy the old one -

print(to_str(t))
# (node 1 (node 2 (node 3 _ _) (node 4 _ _)) (node 5 (node 6 _ _) (node 7 _ _)))

print(to_str(delete(t, 2)))
# (node 1 _ (node 5 (node 6 _ _) (node 7 _ _)))

print(to_str(delete(t, 3)))
# (node 1 (node 2 _ (node 4 _ _)) (node 5 (node 6 _ _) (node 7 _ _)))

print(to_str(t))
# (node 1 (node 2 (node 3 _ _) (node 4 _ _)) (node 5 (node 6 _ _) (node 7 _ _)))

functional backend, object-oriented frontend

If you want to add functions as object methods to some sort of tree class -

def to_str (root = None):
  # defined above ...

def delete (root = None, v = None):
  # defined above ...

class tree:
  def __init__(self, root = None):
    self.root = root

  def __str__(self):
    return to_str(self.root)          # <--

  def delete(self, v = None):
    return tree(delete(self.root, v)) # <--

This gives you the same immutable (persistent) functionality with the more familiar object-oriented interface -

print(tree(t))
# (node 1 (node 2 (node 3 _ _) (node 4 _ _)) (node 5 (node 6 _ _) (node 7 _ _)))

print(tree(t).delete(2))
# (node 1 _ (node 5 (node 6 _ _) (node 7 _ _)))

print(tree(t).delete(3))
# (node 1 (node 2 _ (node 4 _ _)) (node 5 (node 6 _ _) (node 7 _ _)))

print(tree(t))
# (node 1 (node 2 (node 3 _ _) (node 4 _ _)) (node 5 (node 6 _ _) (node 7 _ _)))

functional programming

Functional programming is strong because the program's shape harmonises with the data's shape. Using functions, we can capture the essence of a procedure and reuse it in practical ways -

def identity (x = None):
  return x

def call (f = identity):
  return lambda *a: f(a)

def fold (root = None, f = call(tuple), init = None):
  if not root:
    return init
  else:
    return f \
      ( root.data
      , fold(root.left, f, init)
      , fold(root.right, f, init)
      )

print(fold(t))
# (1, (2, (3, None, None), (4, None, None)), (5, (6, None, None), (7, None, None)))

Using fold below, notice how to_str doesn't have to concern itself with recursion. We can treat the left and right nodes as pre-folded strings -

def to_str (root = None):
  return fold \
    ( root
    , lambda data, left, right: f"(node {data} {left} {right})"
    , "_"
    )

fold is generic and allows us to write a variety of useful programs -

def sum (root = None):
  return fold \
    ( root
    , lambda data, left, right: data + left + right
    , 0
    )

print(to_str(t))
# (node 1 (node 2 (node 3 _ _) (node 4 _ _)) (node 5 (node 6 _ _) (node 7 _ _)))

print(sum(t))
#28

print(to_str(delete(t, 5)))
# (node 1 (node 2 (node 3 _ _) (node 4 _ _)) _)

print(sum(delete(t, 5)))
# 19

I won't give away the answer to the other part of your question, but here's how we could write maximum -

import inf from math

def maximum (root = None):
  return fold \
    ( root
    , lambda data, left, right: max(data, left, right)
    , -inf
    )

print(maximum(t))
# 7

We could even write delete using fold, if we wanted to -

def delete (root = None, q = None):
  return fold \
    ( root
    , lambda data, left, right:
        node(data, left, right) if data != q else None
    , None
    )

fold is can implement common tree traversals too -

def inorder (root = None):
  return fold \
    ( root
    , lambda data, left, right: [ data, *left, *right ]
    , []
    )

def preorder (root = None):
  return fold \
    ( root
    , lambda data, left, right: [ *left, data, *right ]
    , []
    )

def postorder (root = None):
  return fold \
    ( root
    , lambda data, left, right: [ *left, *right, data ]
    , []
    )

Here's t one more time for reference -

       1
      / \
     /   \
    2     5
   / \   / \
  3   4 6   7
print(inorder(t))
# [1, 2, 3, 4, 5, 6, 7]

print(preorder(t))
# [3, 2, 4, 1, 6, 5, 7]

print(postorder(t))
# [3, 4, 2, 6, 7, 5, 1]

expanding the frontend

functionals like fold made it much easier to work with nodes. We can go back and add these to our tree class, if we wanted -

class tree:
  # def __init__ ...
  # def __str__ ...
  # def delete ...

  def fold(self, f = call(tuple), init = None):
    return fold(self.root, f, init)  # <--

  def sum(self):
    return sum(self.root)            # <--

  def max(self)
    return maximum(self.root)        # <--

  def inorder(self):
    return inorder(self.root)        # <--

  def preorder(self):
    return preorder(self.root)       # <--

  def postorder(self):
    return postorder(self.root)      # <--

Usage is comfortable and familiar -

print(tree(t).inorder())
# [1, 2, 3, 4, 5, 6, 7]

print(tree(t).preorder())
# [3, 2, 4, 1, 6, 5, 7]

print(tree(t).postorder())
# [3, 4, 2, 6, 7, 5, 1]

print(tree(t).sum())
# 28

print(tree(t).max())
# 7

We can chain many tree operations together and even fold inline -

print(tree(t).delete(7).delete(6).max())
# 5

print(tree(t).fold(lambda v, l, r: [[ v, *l, *r ]], []))
# [[1, [2, [3], [4]], [5, [6], [7]]]]

print(tree(t).delete(3).delete(7).fold(lambda v, l, r: [[ v, *l, *r ]], []))
# [1, [2, [4]], [5, [6]]]]

time to relax

As we've seen with various examples, fold works over the entire tree to compute a value. But this is not always desirable. Consider a search function that looks for a value in the tree. After the value is matched, what is the purpose in searching deeper into the tree?

Python generators are lazy, totally relaxed, and seamlessly interop with ordinary functions.

def inorder (root = None):       # updated definition!
  def lazy (data, left, right):
    print("computing:", data)    # <-- print just for demo purposes
    yield data                   
    yield from left              # <-- lazy
    yield from right             # <-- lazy
  return fold(root, lazy, [])    # <-- normal call to fold

def zip_tree(tx = None, ty = None, traverse = inorder):
  return zip(traverse(tx), traverse(ty))  # <-- python zip

def equal (tx = None, ty = None):
  for (x, y) in zip_tree(tx, ty):
    print("equal?", x, y)        # <-- print just for demo purposes
    if x != y:
      return False
  return True

print(equal(t, t))

Two trees are equal only if all node values are equal to one another

computing: 1 # tx
computing: 1 # ty
equal? 1 1   # (x, y)
computing: 2 # tx
computing: 2 # ty
equal? 2 2   # (x, y)
computing: 3 # tx
computing: 3 # ty
equal? 3 3   # (x, y)
computing: 4 # tx
computing: 4 # ty
equal? 4 4   # (x, y)
computing: 5 # tx
computing: 5 # ty
equal? 5 5   # (x, y)
computing: 6 # tx
computing: 6 # ty
equal? 6 6   # (x, y)
computing: 7 # tx
computing: 7 # ty
equal? 7 7   # (x, y)
True         # <-- answer

But we can conclude two trees are unequal as soon as one pair of node values is unequal -

print(equal(t, delete(t, 4)))
computing: 1  # tx
computing: 1  # ty
equal? 1 1    # (x, y)
computing: 2  # tx
computing: 2  # ty
equal? 2 2    # (x, y)
computing: 3  # tx
computing: 4  # ty
equal? 3 4    # (x, y)
False         # <-- answer

Demonstrated above, our new lazy inorder does not continue with computation when equal returns an early False result.

Let's remove the print effects and update each inorder, preorder, and postorder with these more so-called Pythonic programs -

def inorder (root = None):
  def lazy (data, left, right):
    yield data           # <-- inorder
    yield from left
    yield from right
  return fold(root, lazy, [])

def preorder (root = None):
  def lazy (data, left, right):
    yield from left
    yield data          # <-- preorder
    yield from right
  return fold(root, lazy, [])

def postorder (root = None):
  def lazy (data, left, right):
    yield from left
    yield from right
    yield data          # <-- postorder
  return fold(root, lazy, [])

def zip_tree (tx = None, ty = None, traverse = inorder):
  return zip(traverse(tx), traverse(ty))  # <-- python zip

def equal (tx = None, ty = None):
  for (x, y) in zip_tree(tx, ty):
    if x != y:
      return False
  return True

Our tree class automatically benefits from these updated lazy inorder, preorder, and postorder traversals. Don't forget to add zip_tree and equal -

class tree:
  # def __init__ ...
  # def __str__ ...
  # def delete ...
  # def fold ...
  # def sum ...
  # def max ...
  # def inorder ...
  # def preorder ...
  # def postorder ...

  def zip(self, other):
    return zip_tree(self.root, other.root) # <-- zip_tree

  def equal(self, other):
    return equal(self.root, other.root)    # <-- equal
print(tree(t).equal(tree(t)))
# True

print(tree(t).equal(tree(t).delete(3)))
# False

print(list(tree(t).zip(tree(t))))
# [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7)]

print([ x * y for (x, y) in tree(t).zip(tree(t)) ])
# [1, 4, 9, 16, 25, 36, 49]

pythonic

This is just a way of saying do things the Python way. zip_tree and equal show us how we can write programs to support our tree. Writing pythonic programs means we use Python conventions where possible -

class node:
  # def __init__ ...

  def __iter__(self):      # <-- __iter__ defines iterator
    return inorder(self)

class tree:
  # def __init__ ...
  # def __str__ ...
  # def delete ...
  # def fold ...
  # def sum ...
  # def max ...
  # def inorder ...
  # def preorder ...
  # def postorder ...

  def __iter__(self):        # <--
    return iter(self.root or []) 

  def equal(self, other):
  def __eq__(self, other):   # <-- __eq__ defines tree equality
    return equal(self.root, other.root)

  def zip(self, other):
    return zip_tree(self.root, other.root)
    return zip(self, other)  # <-- python zip works on all iterables

We no longer need zip_tree -

def zip_tree (tx = None, ty = None, traverse = inorder):
  return zip(traverse(tx), traverse(ty))

def equal (tx = None, ty = None):
  for (x, y) in zip_tree(tx, ty):
  for (x, y) in zip(tx, ty): # <-- use python zip directly on trees
    if x != y:
      return False
  return True

tree.py

Here's a copy of the module we made in this post -

# tree.py
from math import inf

def identity (x = None):
  return x

def call (f = identity):
  return lambda *a: f(a)

def delete (root = None, q = None):
  if not root or root.data == q:
    return None
  else:
    return node(root.data, delete(root.left, q), delete(root.right, q))

def fold (root = None, f = call(tuple), init = None):
  if not root:
    return init
  else:
    return f \
      ( root.data
      , fold(root.left, f, init)
      , fold(root.right, f, init)
      )

def to_str (root = None):
  return fold \
    ( root
    , lambda data, left, right: f"(node {data} {left} {right})"
    , "_"
    )

def maximum (root = None):
  return fold \
    ( root
    , lambda data, left, right: max(data, left, right)
    , -inf
    )

def sum (root = None):
  return fold \
    ( root
    , lambda data, left, right: data + left + right
    , 0
    )

def inorder (root = None):
  def lazy (data, left, right):
    yield data
    yield from left
    yield from right
  return fold(root, lazy, [])

def preorder (root = None):
  def lazy (data, left, right):
    yield from left
    yield data
    yield from right
  return fold(root, lazy, [])

def postorder (root = None):
  def lazy (data, left, right):
    yield from left
    yield from right
    yield data
  return fold(root, lazy, [])

def equal (tx = None, ty = None):
  for (x, y) in zip(tx, ty):
    if x != y:
      return False
  return True

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

  def __iter__ (self):
    return inorder(self)

class tree:
  def __init__ (self, root = None):
    self.root = root

  def __str__ (self):
    return to_str(self.root)

  def delete (self, v = None):
    return tree(delete(self.root, v))

  def fold (self, f = call(tuple), init = None):
    return fold(self.root, f, init)

  def sum (self):
    return sum(self.root)

  def max (self):
    return maximum(self.root)

  def inorder (self):
    return inorder(self.root)

  def preorder (self):
    return preorder(self.root)

  def postorder (self):
    return postorder(self.root)

  def __iter__ (self):
    return iter(self.root or [])

  def __eq__ (self, other):
    return equal(self.root, other.root)

  def zip (self, other):
    return zip(self, other)


来源:https://stackoverflow.com/questions/61840966/deleting-a-node-from-a-binary-search-tree-using-recursion

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!