问题
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