自顶向下的删除
自顶向下的删除方法也是经典教材Robert Sedgewick的Algorithms中所讲述的,有兴趣的读者可以翻看Algorithms第四版。因此,本文在其基础上,讲一讲我对自顶向下删除的理解与书中代码的错误。先上代码。
import matplotlib.pyplot as plt
def is_red(node):
if not node:
return False
return node.color
class Node(object):
def __init__(self, x, left=None, right=None, parent=None, color=False):
"""
:param color: True for red, False for black
"""
self.key = x
self.left = left
self.right = right
self.parent = parent
self.color = color
def __color_flip(self):
if self.parent:
self.color = not self.color
self.left.color = not self.left.color
self.right.color = not self.right.color
def __rotate_left(self):
right = self.right
self.right = right.left
if self.right:
self.right.parent = self
right.left = self
right.color = self.color
right.parent = self.parent
self.__set_parent_children(right)
self.parent = right
self.color = True
def __rotate_right(self):
left = self.left
self.left = left.right
if self.left:
self.left.parent = self
left.right = self
left.color = self.color
left.parent = self.parent
self.__set_parent_children(left)
self.parent = left
self.color = True
def __is_left(self):
if self.parent:
return self.parent.left == self
return False
def __set_parent_children(self, child):
if self.parent:
if self.__is_left():
self.parent.left = child
else:
self.parent.right = child
def add(self, key):
if key < self.key:
if self.left:
self.left.add(key)
else:
self.left = Node(key, parent=self, color=True)
else:
if self.right:
self.right.add(key)
else:
self.right = Node(key, parent=self, color=True)
node = self
if is_red(node.right) and not is_red(node.left):
node.__rotate_left()
node = node.parent
if is_red(node.left) and is_red(node.left.left):
node.__rotate_right()
node = node.parent
if is_red(node.left) and is_red(node.right):
node.__color_flip()
def delete(self, key):
if key < self.key:
if not is_red(self.left) and not is_red(self.left.left):
self.move_red_left()
self.left.delete(key)
else:
node = self
if is_red(node.left):
node.__rotate_right()
node = node.parent
if node.key == key and not node.right:
node.__set_parent_children(None)
node.parent.balance()
return
if not is_red(node.right) and not is_red(node.right.left):
node.move_red_right()
if node.key == key:
min_node = node.right
if min_node:
while min_node.left:
min_node = min_node.left
node.key = min_node.key
node.right.delete(min_node.key)
else:
node.right.delete(key)
def move_red_left(self):
self.__color_flip()
if is_red(self.right.left):
self.right.__rotate_right()
self.__rotate_left()
def move_red_right(self):
self.__color_flip()
if is_red(self.left.left):
self.__rotate_right()
def balance(self):
node = self
while node:
if is_red(node.right):
node.__rotate_left()
node = node.parent
if is_red(node.left) and is_red(node.left.left):
node.__rotate_right()
node = node.parent
if is_red(node.left) and is_red(node.right):
node.__color_flip()
node = node.parent
def draw(self, x=0, y=0, is_root=False, height=0, is_terminal=False):
if not is_root and is_terminal:
assert not self.color
plt.axis('off')
node = self
if not is_root:
while node.parent:
node = node.parent
circle_color = 'black' if not node.color else 'red'
plt.scatter(x, y, color=circle_color, s=1000)
text_color = 'white' if circle_color == 'black' else 'black'
plt.text(x, y, node.key, fontsize='large', color=text_color, horizontalalignment='center',
verticalalignment='center')
delta_x = 0.6 ** (height - 10)
delta_y = 0.6 ** (height + 10)
if node.left:
assert node.left.parent == node
if is_terminal:
assert self.key > self.left.key
assert not (self.left.color and self.color)
line_color = 'black' if not node.left.color else 'red'
plt.plot([x, x - delta_x], [y, y - delta_y], color=line_color)
node.left.draw(x - delta_x, y - delta_y, is_root=True, height=height + 1)
if node.right:
assert node.right.parent == node
if is_terminal:
assert self.key < self.right.key
assert not (self.right.color and self.color)
line_color = 'black' if not node.right.color else 'red'
plt.plot([x, x + delta_x], [y, y - delta_y], color=line_color)
node.right.draw(x + delta_x, y - delta_y, is_root=True, height=height + 1)
if not is_root:
plt.show()
class RBT(object):
def __init__(self, x):
self.__root = Node(x)
def add(self, x):
result = self.__root.add(x)
while self.__root.parent:
self.__root = self.__root.parent
# self.__root.draw(is_terminal=True)
return result is not False
def delete(self, x):
if not self.__root.left and not self.__root.right:
print('最后一个节点')
return
result = self.__root.delete(x)
while self.__root.parent:
self.__root = self.__root.parent
self.__root.draw(is_terminal=True)
return result
def draw(self):
self.__root.draw()
思路
书中代码有两个错误,分别位于__color_flip
与move_red_right
。我在研究书中代码时便感觉奇怪,书中代码不符合整体的代码思路,在查阅源码后,发现这两处的确有误。
首先说说__rotate_left
与__rotate_right
。本质上,这两个操作是在不破坏树的平衡性(多有叶节点的深度相等)的前提下,改变红线方向的操作。
整体思路书中说的比较多,其实简单的说就一句话:将要删除的节点放在3-节点,甚至4-节点中进行删除,然后再对树进行修复。这个思路应该还比较好理解,但书中源码的一些细节性的操作令我很困惑,例如代码第92行:为什么当前节点的左子节点为红时,就要进行左旋?
解答这个问题要先思考,自顶向下的删除与自底向上的删除的本质区别在哪?区别在于,自顶向下的删除在向上修复的过程中并不考虑当前节点的兄弟节点是什么情况,只考虑父节点的具体情况,根据其父节点的具体情况进行不同操作;而自底向上的删除在向上修复的过程中则不仅考虑了当前节点的父节点的具体情况,还考虑了其兄弟节点的具体情况,根据具体情况作出更详细的分类讨论。
基于此,自顶向下的删除,在向下的路径中必须保证另一侧的子节点(路径上的节点的兄弟节点)在未来的修复过程中不会发生错误。因此当路径另一侧离路径较近的子节点中有红键时,需要将红键旋转到路径上进行统一修复。若能想通这一点,代码就比较好理解了。
总结
比较两种方法,显然自顶向下的方法较简单,但理解上有困难,并且很难想到;而自底向上的删除虽然分类讨论比较复杂,但思路便于理解,比较容易想到。在完全理解两种方法后,我感到受益匪浅,也算是完全的理解了红黑树的删除。在此,与大家交流与分享。