遵循与Build the Forest in Series中的其他文章相同的样式和假设,该实现假定使用Python 3.9或更高版本。本文向我们的项目添加了两个模块:red_black_tree.py用于实现红黑树实现,test_red_black_tree.py用于其单元测试。添加这两个文件后,我们的项目布局变为以下内容:
forest-python
├── forest
│ ├── __init__.py
│ ├── binary_trees
│ │ ├── __init__.py
│ │ ├── binary_search_tree.py
│ │ ├── double_threaded_binary_tree.py
│ │ ├── red_black_tree.py
│ │ ├── single_threaded_binary_trees.py
│ │ └── traversal.py
│ └── tree_exceptions.py
└── tests
├── __init__.py
├── conftest.py
├── test_binary_search_tree.py
├── test_double_threaded_binary_tree.py
├── test_red_black_tree.py
├── test_single_threaded_binary_trees.py
└── test_traversal.py
红黑树通过自身平衡能力来改变二叉搜索树,并且它的节点具有一个额外的属性:颜色,可以是红色或黑色。除二进制搜索树属性外,一棵红黑树还满足以下红黑树属性:
红黑树通过限制从节点到其后代叶子的任何简单路径上的节点颜色来确保没有路径比其他路径长两倍。换句话说,一棵红黑树大约是平衡的。
树数据结构的叶节点通常是指没有任何子节点的节点。但是,我们使用NIL表示红黑树的叶子,并且它们始终是黑色的。此外,叶节点不保存任何数据,主要用于维护红黑树属性。
我们使用黑色高度指示从节点到叶子的黑色节点数(如果该节点为黑色,则不包括该节点)。下图显示了每个节点的黑色高度。
每个节点旁边是该节点的黑色高度,叶节点(NIL)的黑色高度为零。
就像我们在前面的树中所做的那样,本节将逐步介绍实现并讨论实现选择背后的一些想法。
由于所有叶子都是NIL,并且根节点的父节点也可以指向NIL,因此当我们实现一棵红黑树时,我们可以定义一个NIL节点,并使根节点的父节点和所有支持的节点指向NIL节点,NIL节点。因此,上一部分中显示的红黑树如下所示:
这种方式简化了实现并节省了空间(即,在实现中仅需要一个NIL节点实例)。
为简单起见,在本文的其余部分中将省略NIL节点,因此上面的红黑树将如下所示(但在实现中,NIL节点必须位于该树中;否则,它将违反红黑树-树属性)。
红黑树节点类似于二叉搜索树节点,但还有一个属性-颜色。
由于颜色必须是红色或黑色,因此我们可以将其定义为枚举类。
import enum
class Color(enum.Enum):
RED = enum.auto()
BLACK = enum.auto()
为什么要使用枚举?
根据PEP-435,“枚举是绑定到唯一,恒定值的一组符号名称。在枚举中,可以通过标识比较这些值,并且可以迭代枚举本身。” 我们将color属性定义为一个枚举类是有道理的,因为它的值(红色或黑色)是恒定的,并且我们可以识别color属性并进行比较。同样,枚举类提供了更强大的类型安全性。如果不使用枚举,则可以将color属性定义为常量字符串。但是,类型检查工具(例如mypy)将无法检查值是color属性还是常规字符串。另一种选择是将color属性定义为常规类或数据类,如下所示:
@dataclass
class Color:
RED: str = "red"
BLACK: str = "black"
这种方法仍然有一些缺点。例如,下划线类型仍然是字符串。我们可以将其与任何字符串进行比较。此外,一类是可变的。换句话说,我们可以在运行时修改与颜色定义矛盾的Color类。因此,使用枚举定义颜色属性最有意义。它增加了类型安全性并简化了实现。
红黑树节点
像其他二叉树节点一样,我们利用数据类来定义红黑树节点。
from dataclasses import dataclass
@dataclass
class Node:
"""Red-Black Tree non-leaf node definition."""
key: Any
data: Any
left: Union["Node", Leaf] = Leaf()
right: Union["Node", Leaf] = Leaf()
parent: Union["Node", Leaf] = Leaf()
color: Color = Color.RED
叶节点
正如红黑树定义中提到的那样,我们使用NIL来指示叶节点,可以如下定义它。
from dataclasses import dataclass
@dataclass
class Leaf:
color = Color.BLACK
像Build the Forest项目中的其他类型的二叉树一样,红黑树类具有相似的功能。
class RBTree:
def __init__(self) -> None:
self._NIL: Leaf = Leaf()
self.root: Union[Node, Leaf] = self._NIL
def __repr__(self) -> str:
"""Provie the tree representation to visualize its layout."""
if self.root:
return (
f"{type(self)}, root={self.root}, "
f"tree_height={str(self.get_height(self.root))}"
)
return "empty tree"
def search(self, key: Any) -> Optional[Node]:
…
def insert(self, key: Any, data: Any) -> None:
…
def delete(self, key: Any) -> None:
…
@staticmethod
def get_leftmost(node: Node) -> Node:
…
@staticmethod
def get_rightmost(node: Node) -> Node:
…
@staticmethod
def get_successor(node: Node) -> Union[Node, Leaf]:
…
@staticmethod
def get_predecessor(node: Node) -> Union[Node, Leaf]:
…
@staticmethod
def get_height(node: Union[Leaf, Node]) -> int:
…
def inorder_traverse(self) -> traversal.Pairs:
…
def preorder_traverse(self) -> traversal.Pairs:
…
def postorder_traverse(self) -> traversal.Pairs:
…
def _transplant(
self, deleting_node: Node, replacing_node: Union[Node, Leaf]
) -> None:
…
def _left_rotate(self, node_x: Node) -> None:
…
def _right_rotate(self, node_x: Node) -> None:
…
def _insert_fixup(self, fixing_node: Node) -> None:
…
def _delete_fixup(self, fixing_node: Union[Leaf, Node]) -> None:
…
请注意,除了所有二叉树的通用方法外,RBTree类还有一些其他方法。_left_rotate() ,_right_rotate() ,_insert_fixup() ,和_delete_fixup()是辅助功能以保持插入和删除后红黑树属性。插入和删除操作均会修改树;因此,该操作可能会违反红黑树属性。这就是为什么我们需要这些功能。
叶子节点是NIL,因此二叉树遍历的遍历例程(使用None来确定是否到达叶子节点)不适用于RBTree类(需要使用Leaf来确定是否到达叶子节点)。因此,RBTree类需要提供其遍历例程。
由于插入操作会修改红黑树,因此结果可能会违反红黑树属性(对于删除也是如此)。为了恢复红黑树的属性,我们需要更改某些节点的颜色,并更新红黑树的结构。更新二叉树结构的方法称为旋转。解决违规的红黑树属性的技术来自算法导论,而红黑树插入的思想可以归纳为以下几点:
由于新节点为红色,因此我们可以违反red-black-tree-property –如果一个节点为红色,则其两个子节点都为黑色,我们可以在插入后修复冲突。
我们可以通过与普通的二进制搜索树插入类似的方式来实现红黑树插入。
def insert(self, key: Any, data: Any) -> None:
# Color the new node as red.
new_node = Node(key=key, data=data, color=Color.RED)
parent: Union[Node, Leaf] = self._NIL
current: Union[Node, Leaf] = self.root
while isinstance(current, Node): # Look for the insert location
parent = current
if new_node.key < current.key:
current = current.left
elif new_node.key > current.key:
current = current.right
else:
raise tree_exceptions.DuplicateKeyError(key=new_node.key)
new_node.parent = parent
# If the parent is a Leaf, set the new node to be the root.
if isinstance(parent, Leaf):
new_node.color = Color.BLACK
self.root = new_node
else:
if new_node.key < parent.key:
parent.left = new_node
else:
parent.right = new_node
# After the insertion, fix the broken red-black-tree-property.
self._insert_fixup(new_node)
与二叉搜索树的不同之处在于,我们使用isinstance来检查节点是普通节点还是叶子节点,而不是检查它是否为None。那是因为我们有叶子节点的叶子类型和常规节点的节点类型。
将新节点插入红黑树后,我们需要修复损坏的红黑树属性。以下小节将讨论使用旋转和着色来修复折断的红黑树。
轮换
旋转操作是为了更改红黑树的结构,并保留其二进制搜索属性。旋转有两种类型:左旋转和右旋转。
左旋
左旋转将两个节点(图片中的x和y)从图片的顶部树转移到底部树,并保留其binary-search-tree-properties。
def _left_rotate(self, node_x: Node) -> None:
node_y = node_x.right # Set node y
if isinstance(node_y, Leaf): # Node y cannot be a Leaf
raise RuntimeError("Invalid left rotate")
# Turn node y's subtree into node x's subtree
node_x.right = node_y.left
if isinstance(node_y.left, Node):
node_y.left.parent = node_x
node_y.parent = node_x.parent
# If node's parent is a Leaf, node y becomes the new root.
if isinstance(node_x.parent, Leaf):
self.root = node_y
# Otherwise, update node x's parent.
elif node_x == node_x.parent.left:
node_x.parent.left = node_y
else:
node_x.parent.right = node_y
node_y.left = node_x
node_x.parent = node_y
请注意,节点x的右子不能为Leaf(即NIL);旋转叶子毫无意义。
右旋
右旋与左旋对称,可以实现如下。
def _right_rotate(self, node_x: Node) -> None:
node_y = node_x.left # Set node y
if isinstance(node_y, Leaf): # Node y cannot be a Leaf
raise RuntimeError("Invalid right rotate")
# Turn node y's subtree into node x's subtree
node_x.left = node_y.right
if isinstance(node_y.right, Node):
node_y.right.parent = node_x
node_y.parent = node_x.parent
# If node's parent is a Leaf, node y becomes the new root.
if isinstance(node_x.parent, Leaf):
self.root = node_y
# Otherwise, update node x's parent.
elif node_x == node_x.parent.right:
node_x.parent.right = node_y
else:
node_x.parent.left = node_y
node_y.right = node_x
node_x.parent = node_y
修理
要恢复red-black-tree-property,我们需要知道插入后哪些red-black-property可能被破坏。
红黑树属性:
对于第5个属性,黑色高度仍然相同。新节点(如红色)替换了Leaf(NIL),但是它的子节点也是Leaf(NIL)。因此,黑色高度属性在插入后保持不变。
因此,由于新节点的颜色为红色,因此只有属性2和属性4可能会受到侵犯。如果新节点的父节点也为红色,则属性4损坏。关于属性2,如果在固定属性4时新节点是根或根变成红色,则可能会违反该属性。我们可以通过在固定例程的末尾将其着色为黑色来固定属性2。
关于固定特性4,我们可以将其细分为6种情况(对称地分为3种情况)。这六种情况由新节点的父级的位置和颜色以及父级的同级决定。
|
新节点的父母的位置 |
新节点的父级兄弟姐妹的颜色 |
新节点的位置 |
情况1 |
左子 |
红色的 |
没关系 |
情况二 |
左子 |
黑色的 |
合适的孩子 |
情况3 |
左子 |
黑色的 |
左子 |
案例4 |
合适的孩子 |
红色的 |
没关系 |
案例5 |
合适的孩子 |
黑色的 |
左子 |
案例6 |
合适的孩子 |
黑色的 |
合适的孩子 |
从新节点(fix_node)开始。
情况1
请注意,新节点的位置无关紧要。下图显示了情况1.新节点(7)是左子节点,而2.新节点(18)是右子节点。
情况二
情况3
情况4(与情况1相同)
案例5
案例6
在为fixing_node修复损坏的红黑树属性时,修复过程可能会导致fixing_node的父级或祖父母(视情况而定)违反红黑树属性。发生这种情况时,fixing_node的父(或祖父母)将成为新的fixing_node。然后我们可以根据这六个案例解决它。重复此步骤,直到到达根目录为止。如果在修复操作之后根变成红色(违反了属性2),我们可以通过将根着色为黑色来修复它。
下图演示了构建红黑树的完整插入操作。
插入固定的实现如下所示。
def _insert_fixup(self, fixing_node: Node) -> None:
while fixing_node.parent.color == Color.RED:
if fixing_node.parent == fixing_node.parent.parent.left:
parent_sibling = fixing_node.parent.parent.right
if parent_sibling.color == Color.RED: # Case 1
fixing_node.parent.color = Color.BLACK
parent_sibling.color = Color.BLACK
fixing_node.parent.parent.color = Color.RED
fixing_node = fixing_node.parent.parent
else:
# Case 2
if fixing_node == fixing_node.parent.right:
fixing_node = fixing_node.parent
self._left_rotate(fixing_node)
# Case 3
fixing_node.parent.color = Color.BLACK
fixing_node.parent.parent.color = Color.RED
self._right_rotate(fixing_node.parent.parent)
else:
parent_sibling = fixing_node.parent.parent.left
if parent_sibling.color == Color.RED: # Case 4
fixing_node.parent.color = Color.BLACK
parent_sibling.color = Color.BLACK
fixing_node.parent.parent.color = Color.RED
fixing_node = fixing_node.parent.parent
else:
# Case 5
if fixing_node == fixing_node.parent.left:
fixing_node = fixing_node.parent
self._right_rotate(fixing_node)
# Case 6
fixing_node.parent.color = Color.BLACK
fixing_node.parent.parent.color = Color.RED
self._left_rotate(fixing_node.parent.parent)
self.root.color = Color.BLACK
搜索操作类似于二进制搜索树,但是我们使用Leaf(NIL)确定是否到达叶子。
该搜索的实现方式类似于二叉搜索树搜索功能。
def search(self, key: Any) -> Optional[Node]:
current = self.root
while isinstance(current, Node):
if key < current.key:
current = current.left
elif key > current.key:
current = current.right
else: # Key found
return current
# If the tree is empty (i.e., self.root == Leaf()), still return None.
return None
与插入红黑树相似,删除操作会修改红黑树,结果可能会违反红黑树属性。因此,我们需要在删除节点后恢复red-black-tree-property。
红黑树删除的基本思想类似于普通的二进制搜索树。我们有一个移植的方法,以取代在根的子树deleting_node与在根的子树replacing_node。我们还有三种基本的删除案例:无孩子,只有一个孩子和两个孩子。最后,我们需要修复损坏的红黑树属性。
移植
该移植方法是从修改后的二叉搜索树,所以它的工作原理适当的红黑树。修改之处在于,我们使用isinstance来检查节点是普通节点还是Leaf节点,而不是检查它是否为None。
def _transplant(
self, deleting_node: Node, replacing_node: Union[Node, Leaf]
) -> None:
if isinstance(deleting_node.parent, Leaf):
self.root = replacing_node
elif deleting_node == deleting_node.parent.left:
deleting_node.parent.left = replacing_node
else:
deleting_node.parent.right = replacing_node
if isinstance(replacing_node, Node):
replacing_node.parent = deleting_node.parent
整个删除过程就像经过一些修改的普通二进制搜索树。
基于上面的删除过程,我们可以实现以下删除方法。
def delete(self, key: Any) -> None:
if (deleting_node := self.search(key=key)) and (
not isinstance(deleting_node, Leaf)
):
original_color = deleting_node.color
# Case 1: no children or Case 2a: only one right child
if isinstance(deleting_node.left, Leaf):
replacing_node = deleting_node.right
self._transplant(
deleting_node=deleting_node, replacing_node=replacing_node
)
# Fixup
if original_color == Color.BLACK:
if isinstance(replacing_node, Node):
self._delete_fixup(fixing_node=replacing_node)
# Case 2b: only one left child
elif isinstance(deleting_node.right, Leaf):
replacing_node = deleting_node.left
self._transplant(
deleting_node=deleting_node, replacing_node=replacing_node
)
# Fixup
if original_color == Color.BLACK:
self._delete_fixup(fixing_node=replacing_node)
# Case 3: two children
else:
replacing_node = self.get_leftmost(deleting_node.right)
original_color = replacing_node.color
replacing_replacement = replacing_node.right
# The replacing node is not the direct child of the deleting node
if replacing_node.parent != deleting_node:
self._transplant(replacing_node, replacing_node.right)
replacing_node.right = deleting_node.right
replacing_node.right.parent = replacing_node
self._transplant(deleting_node, replacing_node)
replacing_node.left = deleting_node.left
replacing_node.left.parent = replacing_node
replacing_node.color = deleting_node.color
# Fixup
if original_color == Color.BLACK:
if isinstance(replacing_replacement, Node):
self._delete_fixup(fixing_node=replacing_replacement)
值得一提的是:
在还原违规的red-black-tree属性之前,我们需要知道在删除例程期间哪些red-black-property可能会被破坏。
红黑树属性:
情况1:要删除的节点没有子节点
如果颜色deleting_node赤,人无红黑树属性被打破。如果delete_node的颜色为黑色,则违反属性5。
情况2:要删除的节点只有一个孩子
由于具有红黑树属性,一个红色节点不可能只有一个黑人孩子。一个黑色节点也不可能只有一个黑色子节点。因此,如果deleting_node只有一个孩子,它的颜色必须是黑色,而颜色replacing_node必须为红色。
根据上面的图片,如果deleteing_node是黑色,则属性4和属性5以及属性2都可以被破坏(如果deleteingnode是根)。
情况3:要删除的节点有两个子节点
在这种情况下,我们执行以下步骤:
第5步之后,颜色replacing_node是一样的deleting_node,所以没有红黑树属性被打破。这可能会破坏红黑树属性的唯一一步是一步2.当我们进行移植的操作replacing_node,它最终被这两种情况下1或2的情况下。
下图演示了删除具有两个子节点的节点可能会或可能不会违反red-black-tree-property。
该案件replacing_node是直接孩子deleting_node。
该案件replacing_node是黑色的,而不是直接孩子deleting_node。
该案件replacing_node是红色的,而不是直接孩子deleting_node。
因此,我们可以总结需要修复损坏的红黑树属性的情况。
没有孩子 |
如果要删除的节点为黑色 |
只有一个孩子 |
如果要删除的节点为黑色 |
两个孩子 |
如果要替换的节点(从要删除的节点的右子树的最左端)是黑色的 |
该摘要还暗示,在以下情况下,红黑树属性仍然成立。
原因如下:
修理
要修复损坏的红黑树属性,我们使用算法导论中的思想,并且修复过程首先通过引入该概念来修复属性5(从节点到叶子的每个节点的黑色高度都相同)。黑色和红色和黑色。对于黑色高度,双黑和红黑分别贡献2或1。并且,我们使用以下图标表示黑色和红色黑色节点。
当我们使用移植功能将一个节点替换为另一个节点时,我们保留两个节点的颜色,而不是替换该节点,并使用双黑和红黑两种颜色表示其颜色。因此,如果要删除的节点少于两个子节点,则在移植功能之后,替换节点的颜色将变为双黑或红黑。如果要删除的节点有两个子节点,则当我们使用移植功能取出要删除的节点所扎根的子树的最左边的节点时,最左边的节点替换的颜色将变为黑色或红色和黑色。。为了简单起见,我们使用fixing_node指示节点的颜色为双黑或红黑。请注意,在代码实现中,我们并没有真正将fixing_node着色为双黑或红黑。我们只是假装fixing_node有一个额外的黑色或红色。
这样,可以解决无效的黑色高度,并且可能出现的破损情况如下:
要删除的节点没有子节点
要删除的节点只有一个孩子
要删除的节点有两个子节点
如果要删除的节点有两个子节点,则位于最左侧节点位置的节点将破坏红黑树属性。
该replacing_node是直接孩子deleting_node。
该replacing_node不是直接孩子deleting_node。
由于fixing_node的颜色既不是红色也不是黑色,因此属性1也被破坏了。现在,我们需要还原red-black-tree-property 1、2和4。
如果fixing_node的颜色是红色和黑色,我们可以通过将其着色为黑色来对其进行修复。恢复所有损坏的红黑树属性。
剩下的残破情况是双黑的。修复程序可以分为四个对称情况。
该案件是由位置确定fixing_node的颜色fixing_node的兄弟姐妹,以及兄弟姐妹的子女的颜色。
|
fixing_node |
兄弟姐妹的颜色 |
兄弟姐妹左孩子的颜色 |
兄弟姊妹的孩子的颜色 |
情况1 |
左子 |
红色的 |
没关系 |
没关系 |
情况二 |
左子 |
黑色的 |
黑色的 |
黑色的 |
情况3 |
左子 |
黑色的 |
红色的 |
黑色的 |
案例4 |
左子 |
黑色的 |
黑色的 |
红色的 |
案例5 |
合适的孩子 |
红色的 |
没关系 |
没关系 |
案例6 |
合适的孩子 |
黑色的 |
黑色的 |
黑色的 |
案例7 |
合适的孩子 |
黑色的 |
黑色的 |
红色的 |
案例8 |
合适的孩子 |
黑色的 |
红色的 |
黑色的 |
从fixing_node开始。
情况1
情况二
情况3
案例4
案例5
案例6
案例7
案例8
以下实现总结了上面讨论的修复过程。
def _delete_fixup(self, fixing_node: Union[Leaf, Node]) -> None:
while (fixing_node is not self.root) and (fixing_node.color == Color.BLACK):
if fixing_node == fixing_node.parent.left:
sibling = fixing_node.parent.right
# Case 1: the sibling is red.
if sibling.color == Color.RED:
sibling.color == Color.BLACK
fixing_node.parent.color = Color.RED
self._left_rotate(fixing_node.parent)
sibling = fixing_node.parent.right
# Case 2: the sibling is black and its children are black.
if (sibling.left.color == Color.BLACK) and (
sibling.right.color == Color.BLACK
):
sibling.color = Color.RED
fixing_node = fixing_node.parent # new fixing node
# Cases 3 and 4: the sibling is black and one of
# its child is red and the other is black.
else:
# Case 3: the sibling is black and its left child is red.
if sibling.right.color == Color.BLACK:
sibling.left.color = Color.BLACK
sibling.color = Color.RED
self._right_rotate(node_x=sibling)
# Case 4: the sibling is black and its right child is red.
sibling.color = fixing_node.parent.color
fixing_node.parent.color = Color.BLACK
sibling.right.color = Color.BLACK
self._left_rotate(node_x=fixing_node.parent)
# Once we are here, all the violation has been fixed, so
# move to the root to terminate the loop.
fixing_node = self.root
else:
sibling = fixing_node.parent.left
# Case 5: the sibling is red.
if sibling.color == Color.RED:
sibling.color == Color.BLACK
fixing_node.parent.color = Color.RED
self._right_rotate(node_x=fixing_node.parent)
sibling = fixing_node.parent.left
# Case 6: the sibling is black and its children are black.
if (sibling.right.color == Color.BLACK) and (
sibling.left.color == Color.BLACK
):
sibling.color = Color.RED
fixing_node = fixing_node.parent
else:
# Case 7: the sibling is black and its right child is red.
if sibling.left.color == Color.BLACK:
sibling.right.color = Color.BLACK
sibling.color = Color.RED
self._left_rotate(node_x=sibling)
# Case 8: the sibling is black and its left child is red.
sibling.color = fixing_node.parent.color
fixing_node.parent.color = Color.BLACK
sibling.left.color = Color.BLACK
self._right_rotate(node_x=fixing_node.parent)
# Once we are here, all the violation has been fixed, so
# move to the root to terminate the loop.
fixing_node = self.root
fixing_node.color = Color.BLACK
除了核心功能(即插入,搜索和删除)之外,红黑树还可以具有其他有用的功能,例如获取最左边的节点,获取节点的后继者以及获取树的高度,其实现类似于二叉搜索树辅助功能,但有一些修改。
取得身高
要计算一棵红黑树的树高,我们可以像在二叉搜索树中一样,对每个孩子的身高递归地将其高度加一。如果一个节点有两个子节点,则使用max函数从这些子节点获取更大的高度,并将最高的子节点增加一个。主要区别在于我们使用isinstance检查节点是否具有叶子。
@staticmethod
def get_height(node: Union[Leaf, Node]) -> int:
if isinstance(node, Node):
if isinstance(node.left, Node) and isinstance(node.right, Node):
return (
max(RBTree.get_height(node.left), RBTree.get_height(node.right)) + 1
)
if isinstance(node.left, Node):
return RBTree.get_height(node=node.left) + 1
if isinstance(node.right, Node):
return RBTree.get_height(node=node.right) + 1
return 0
获取最左边和最右边的节点
红黑树与二叉搜索树的功能相同,它可以获取给定(子)树的最右边节点和给定(子)树的最左边节点。再次,我们使用isinstance检查节点是常规的红黑树节点还是叶子。
最左边
@staticmethod
def get_leftmost(node: Node) -> Node:
current_node = node
while isinstance(current_node.left, Node):
current_node = current_node.left
return current_node
最右边
@staticmethod
def get_rightmost(node: Node) -> Node:
current_node = node
while isinstance(current_node.right, Node):
current_node = current_node.right
return current_node
前任和后继
红黑树与二进制搜索树的功能相同,它可以获取节点的前任和后继。
前任
@staticmethod
def get_predecessor(node: Node) -> Union[Node, Leaf]:
if isinstance(node.left, Node):
return RBTree.get_rightmost(node=node.left)
parent = node.parent
while isinstance(parent, Node) and node == parent.left:
node = parent
parent = parent.parent
return node.parent
接班人
@staticmethod
def get_successor(node: Node) -> Union[Node, Leaf]:
if isinstance(node.right, Node):
return RBTree.get_leftmost(node=node.right)
parent = node.parent
while isinstance(parent, Node) and node == parent.right:
node = parent
parent = parent.parent
return parent
由于叶节点的原因,我们无法使用在二叉树遍历中实现的遍历函数。但是,遍历的概念是相同的。我们只需要简单的修改即可:使用isinstance检查节点是否为常规的红黑树节点或叶子。
有序遍历
def inorder_traverse(self) -> traversal.Pairs:
return self._inorder_traverse(node=self.root)
def _inorder_traverse(self, node: Union[Node, Leaf]) -> traversal.Pairs:
if isinstance(node, Node):
yield from self._inorder_traverse(node.left)
yield (node.key, node.data)
yield from self._inorder_traverse(node.right)
预购遍历
def preorder_traverse(self) -> traversal.Pairs:
return self._preorder_traverse(node=self.root)
def _preorder_traverse(self, node: Union[Node, Leaf]) -> traversal.Pairs:
if isinstance(node, Node):
yield (node.key, node.data)
yield from self._preorder_traverse(node.left)
yield from self._preorder_traverse(node.right)
后遍历
def postorder_traverse(self) -> traversal.Pairs:
return self._postorder_traverse(node=self.root)
def _postorder_traverse(self, node: Union[Node, Leaf]) -> traversal.Pairs:
if isinstance(node, Node):
yield from self._postorder_traverse(node.left)
yield from self._postorder_traverse(node.right)
yield (node.key, node.data)
注意,返回类型(traversal.Pairs)在定义traversal.py从二叉树的遍历。
与往常一样,我们应该对我们的代码进行尽可能多的单元测试。在这里,我们使用basic_tree功能conftest.py我们在创建构建二叉搜索树来测试我们的红黑树。检查test_red_black_tree.py以进行完整的单元测试。
正如我们在“二分搜索树:分析”中讨论的那样,我们知道二分搜索树的操作运行时基于树的高度。红黑树是一种自平衡二叉搜索树,高度最大为2 * lg(n + 1)= O(lg n),其中n是节点数。(证明可参考算法引理13.1)。因此,可以在下表中总结一棵红黑树的时间复杂度。
由于具有自平衡功能,红黑树在软件程序中广泛使用,包括实现其他数据结构。例如,C ++ STL映射被实现为一棵红黑树。本节使用我们在此处实现的红黑树来实现键值Map。
from typing import Any, Optional
from forest.binary_trees import red_black_tree
from forest.binary_trees import traversal
class Map:
"""Key-value Map implemented using Red-Black Tree."""
def __init__(self) -> None:
self._rbt = red_black_tree.RBTree()
def __setitem__(self, key: Any, value: Any) -> None:
"""Insert (key, value) item into the map."""
self._rbt.insert(key=key, data=value)
def __getitem__(self, key: Any) -> Optional[Any]:
"""Get the data by the given key."""
node = self._rbt.search(key=key)
if node:
return node.data
return None
def __delitem__(self, key: Any) -> None:
"""Remove a (key, value) pair from the map."""
self._rbt.delete(key=key)
def __iter__(self) -> traversal.Pairs:
"""Iterate the data in the map."""
return self._rbt.inorder_traverse()
if __name__ == "__main__":
# Initialize the Map instance.
contacts = Map()
# Add some items.
contacts["Mark"] = "[email protected]"
contacts["John"] = "[email protected]"
contacts["Luke"] = "[email protected]"
# Retrieve an email
print(contacts["Mark"])
# Delete one item.
del contacts["John"]
# Check the deleted item.
print(contacts["John"]) # This will print None
# Iterate the items.
for contact in contacts:
print(contact)
完整的示例如下
https://github.com/shunsvineyard/forest-python/blob/main/examples/rbt_map.py