2019-07-25 18:38:24 +00:00
|
|
|
"""
|
2023-03-16 12:31:29 +00:00
|
|
|
psf/black : true
|
|
|
|
ruff : passed
|
2019-07-25 18:38:24 +00:00
|
|
|
"""
|
2024-03-13 06:52:41 +00:00
|
|
|
|
2021-09-07 11:37:03 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2022-07-11 08:19:52 +00:00
|
|
|
from collections.abc import Iterator
|
2019-07-25 18:38:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
class RedBlackTree:
|
|
|
|
"""
|
|
|
|
A Red-Black tree, which is a self-balancing BST (binary search
|
|
|
|
tree).
|
|
|
|
This tree has similar performance to AVL trees, but the balancing is
|
|
|
|
less strict, so it will perform faster for writing/deleting nodes
|
|
|
|
and slower for reading in the average case, though, because they're
|
|
|
|
both balanced binary search trees, both will get the same asymptotic
|
2020-01-18 12:24:33 +00:00
|
|
|
performance.
|
2024-04-22 19:51:47 +00:00
|
|
|
To read more about them, https://en.wikipedia.org/wiki/Red-black_tree
|
2019-07-25 18:38:24 +00:00
|
|
|
Unless otherwise specified, all asymptotic runtimes are specified in
|
|
|
|
terms of the size of the tree.
|
|
|
|
"""
|
|
|
|
|
2020-08-30 19:22:36 +00:00
|
|
|
def __init__(
|
|
|
|
self,
|
2021-09-07 11:37:03 +00:00
|
|
|
label: int | None = None,
|
2020-08-30 19:22:36 +00:00
|
|
|
color: int = 0,
|
2021-09-07 11:37:03 +00:00
|
|
|
parent: RedBlackTree | None = None,
|
|
|
|
left: RedBlackTree | None = None,
|
|
|
|
right: RedBlackTree | None = None,
|
2020-08-30 19:22:36 +00:00
|
|
|
) -> None:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Initialize a new Red-Black Tree node with the given values:
|
2020-09-10 08:31:26 +00:00
|
|
|
label: The value associated with this node
|
|
|
|
color: 0 if black, 1 if red
|
|
|
|
parent: The parent to this node
|
|
|
|
left: This node's left child
|
|
|
|
right: This node's right child
|
2019-07-25 18:38:24 +00:00
|
|
|
"""
|
|
|
|
self.label = label
|
|
|
|
self.parent = parent
|
|
|
|
self.left = left
|
|
|
|
self.right = right
|
|
|
|
self.color = color
|
|
|
|
|
|
|
|
# Here are functions which are specific to red-black trees
|
|
|
|
|
2021-09-07 11:37:03 +00:00
|
|
|
def rotate_left(self) -> RedBlackTree:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Rotate the subtree rooted at this node to the left and
|
|
|
|
returns the new root to this subtree.
|
2020-01-18 12:24:33 +00:00
|
|
|
Performing one rotation can be done in O(1).
|
2019-07-25 18:38:24 +00:00
|
|
|
"""
|
|
|
|
parent = self.parent
|
|
|
|
right = self.right
|
2021-11-04 15:38:43 +00:00
|
|
|
if right is None:
|
|
|
|
return self
|
2019-07-25 18:38:24 +00:00
|
|
|
self.right = right.left
|
|
|
|
if self.right:
|
|
|
|
self.right.parent = self
|
|
|
|
self.parent = right
|
|
|
|
right.left = self
|
|
|
|
if parent is not None:
|
|
|
|
if parent.left == self:
|
|
|
|
parent.left = right
|
|
|
|
else:
|
|
|
|
parent.right = right
|
|
|
|
right.parent = parent
|
|
|
|
return right
|
|
|
|
|
2021-09-07 11:37:03 +00:00
|
|
|
def rotate_right(self) -> RedBlackTree:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Rotate the subtree rooted at this node to the right and
|
|
|
|
returns the new root to this subtree.
|
|
|
|
Performing one rotation can be done in O(1).
|
|
|
|
"""
|
2021-11-04 15:38:43 +00:00
|
|
|
if self.left is None:
|
|
|
|
return self
|
2019-07-25 18:38:24 +00:00
|
|
|
parent = self.parent
|
|
|
|
left = self.left
|
|
|
|
self.left = left.right
|
|
|
|
if self.left:
|
|
|
|
self.left.parent = self
|
|
|
|
self.parent = left
|
|
|
|
left.right = self
|
|
|
|
if parent is not None:
|
|
|
|
if parent.right is self:
|
|
|
|
parent.right = left
|
|
|
|
else:
|
|
|
|
parent.left = left
|
|
|
|
left.parent = parent
|
|
|
|
return left
|
|
|
|
|
2021-09-07 11:37:03 +00:00
|
|
|
def insert(self, label: int) -> RedBlackTree:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Inserts label into the subtree rooted at self, performs any
|
|
|
|
rotations necessary to maintain balance, and then returns the
|
|
|
|
new root to this subtree (likely self).
|
|
|
|
This is guaranteed to run in O(log(n)) time.
|
|
|
|
"""
|
|
|
|
if self.label is None:
|
|
|
|
# Only possible with an empty tree
|
|
|
|
self.label = label
|
|
|
|
return self
|
|
|
|
if self.label == label:
|
|
|
|
return self
|
|
|
|
elif self.label > label:
|
|
|
|
if self.left:
|
|
|
|
self.left.insert(label)
|
|
|
|
else:
|
|
|
|
self.left = RedBlackTree(label, 1, self)
|
|
|
|
self.left._insert_repair()
|
2024-03-28 17:25:41 +00:00
|
|
|
elif self.right:
|
|
|
|
self.right.insert(label)
|
2019-07-25 18:38:24 +00:00
|
|
|
else:
|
2024-03-28 17:25:41 +00:00
|
|
|
self.right = RedBlackTree(label, 1, self)
|
|
|
|
self.right._insert_repair()
|
2019-07-25 18:38:24 +00:00
|
|
|
return self.parent or self
|
|
|
|
|
2020-08-30 19:22:36 +00:00
|
|
|
def _insert_repair(self) -> None:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Repair the coloring from inserting into a tree."""
|
|
|
|
if self.parent is None:
|
|
|
|
# This node is the root, so it just needs to be black
|
|
|
|
self.color = 0
|
|
|
|
elif color(self.parent) == 0:
|
|
|
|
# If the parent is black, then it just needs to be red
|
|
|
|
self.color = 1
|
|
|
|
else:
|
|
|
|
uncle = self.parent.sibling
|
|
|
|
if color(uncle) == 0:
|
|
|
|
if self.is_left() and self.parent.is_right():
|
|
|
|
self.parent.rotate_right()
|
2021-11-04 15:38:43 +00:00
|
|
|
if self.right:
|
|
|
|
self.right._insert_repair()
|
2019-07-25 18:38:24 +00:00
|
|
|
elif self.is_right() and self.parent.is_left():
|
|
|
|
self.parent.rotate_left()
|
2021-11-04 15:38:43 +00:00
|
|
|
if self.left:
|
|
|
|
self.left._insert_repair()
|
2019-07-25 18:38:24 +00:00
|
|
|
elif self.is_left():
|
2021-11-04 15:38:43 +00:00
|
|
|
if self.grandparent:
|
|
|
|
self.grandparent.rotate_right()
|
|
|
|
self.parent.color = 0
|
|
|
|
if self.parent.right:
|
|
|
|
self.parent.right.color = 1
|
2019-07-25 18:38:24 +00:00
|
|
|
else:
|
2021-11-04 15:38:43 +00:00
|
|
|
if self.grandparent:
|
|
|
|
self.grandparent.rotate_left()
|
|
|
|
self.parent.color = 0
|
|
|
|
if self.parent.left:
|
|
|
|
self.parent.left.color = 1
|
2019-07-25 18:38:24 +00:00
|
|
|
else:
|
|
|
|
self.parent.color = 0
|
2021-11-04 15:38:43 +00:00
|
|
|
if uncle and self.grandparent:
|
|
|
|
uncle.color = 0
|
|
|
|
self.grandparent.color = 1
|
|
|
|
self.grandparent._insert_repair()
|
2019-07-25 18:38:24 +00:00
|
|
|
|
2024-04-01 19:36:41 +00:00
|
|
|
def remove(self, label: int) -> RedBlackTree:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Remove label from this tree."""
|
|
|
|
if self.label == label:
|
|
|
|
if self.left and self.right:
|
|
|
|
# It's easier to balance a node with at most one child,
|
|
|
|
# so we replace this node with the greatest one less than
|
|
|
|
# it and remove that.
|
|
|
|
value = self.left.get_max()
|
2021-11-04 15:38:43 +00:00
|
|
|
if value is not None:
|
|
|
|
self.label = value
|
|
|
|
self.left.remove(value)
|
2019-07-25 18:38:24 +00:00
|
|
|
else:
|
|
|
|
# This node has at most one non-None child, so we don't
|
|
|
|
# need to replace
|
|
|
|
child = self.left or self.right
|
|
|
|
if self.color == 1:
|
|
|
|
# This node is red, and its child is black
|
|
|
|
# The only way this happens to a node with one child
|
|
|
|
# is if both children are None leaves.
|
|
|
|
# We can just remove this node and call it a day.
|
2021-11-04 15:38:43 +00:00
|
|
|
if self.parent:
|
|
|
|
if self.is_left():
|
|
|
|
self.parent.left = None
|
|
|
|
else:
|
|
|
|
self.parent.right = None
|
2024-03-28 17:25:41 +00:00
|
|
|
# The node is black
|
|
|
|
elif child is None:
|
|
|
|
# This node and its child are black
|
|
|
|
if self.parent is None:
|
|
|
|
# The tree is now empty
|
|
|
|
return RedBlackTree(None)
|
2019-07-25 18:38:24 +00:00
|
|
|
else:
|
2024-03-28 17:25:41 +00:00
|
|
|
self._remove_repair()
|
|
|
|
if self.is_left():
|
|
|
|
self.parent.left = None
|
|
|
|
else:
|
|
|
|
self.parent.right = None
|
|
|
|
self.parent = None
|
|
|
|
else:
|
|
|
|
# This node is black and its child is red
|
|
|
|
# Move the child node here and make it black
|
|
|
|
self.label = child.label
|
|
|
|
self.left = child.left
|
|
|
|
self.right = child.right
|
|
|
|
if self.left:
|
|
|
|
self.left.parent = self
|
|
|
|
if self.right:
|
|
|
|
self.right.parent = self
|
2021-11-04 15:38:43 +00:00
|
|
|
elif self.label is not None and self.label > label:
|
2019-07-25 18:38:24 +00:00
|
|
|
if self.left:
|
|
|
|
self.left.remove(label)
|
2024-03-28 17:25:41 +00:00
|
|
|
elif self.right:
|
|
|
|
self.right.remove(label)
|
2019-07-25 18:38:24 +00:00
|
|
|
return self.parent or self
|
|
|
|
|
2020-08-30 19:22:36 +00:00
|
|
|
def _remove_repair(self) -> None:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Repair the coloring of the tree that may have been messed up."""
|
2021-11-04 15:38:43 +00:00
|
|
|
if (
|
|
|
|
self.parent is None
|
|
|
|
or self.sibling is None
|
|
|
|
or self.parent.sibling is None
|
|
|
|
or self.grandparent is None
|
|
|
|
):
|
|
|
|
return
|
2019-07-25 18:38:24 +00:00
|
|
|
if color(self.sibling) == 1:
|
|
|
|
self.sibling.color = 0
|
|
|
|
self.parent.color = 1
|
|
|
|
if self.is_left():
|
|
|
|
self.parent.rotate_left()
|
|
|
|
else:
|
|
|
|
self.parent.rotate_right()
|
|
|
|
if (
|
|
|
|
color(self.parent) == 0
|
|
|
|
and color(self.sibling) == 0
|
|
|
|
and color(self.sibling.left) == 0
|
|
|
|
and color(self.sibling.right) == 0
|
|
|
|
):
|
|
|
|
self.sibling.color = 1
|
|
|
|
self.parent._remove_repair()
|
|
|
|
return
|
|
|
|
if (
|
|
|
|
color(self.parent) == 1
|
|
|
|
and color(self.sibling) == 0
|
|
|
|
and color(self.sibling.left) == 0
|
|
|
|
and color(self.sibling.right) == 0
|
|
|
|
):
|
|
|
|
self.sibling.color = 1
|
|
|
|
self.parent.color = 0
|
|
|
|
return
|
|
|
|
if (
|
|
|
|
self.is_left()
|
|
|
|
and color(self.sibling) == 0
|
|
|
|
and color(self.sibling.right) == 0
|
|
|
|
and color(self.sibling.left) == 1
|
|
|
|
):
|
|
|
|
self.sibling.rotate_right()
|
|
|
|
self.sibling.color = 0
|
2021-11-04 15:38:43 +00:00
|
|
|
if self.sibling.right:
|
|
|
|
self.sibling.right.color = 1
|
2019-07-25 18:38:24 +00:00
|
|
|
if (
|
|
|
|
self.is_right()
|
|
|
|
and color(self.sibling) == 0
|
|
|
|
and color(self.sibling.right) == 1
|
|
|
|
and color(self.sibling.left) == 0
|
|
|
|
):
|
|
|
|
self.sibling.rotate_left()
|
|
|
|
self.sibling.color = 0
|
2021-11-04 15:38:43 +00:00
|
|
|
if self.sibling.left:
|
|
|
|
self.sibling.left.color = 1
|
2019-07-25 18:38:24 +00:00
|
|
|
if (
|
|
|
|
self.is_left()
|
|
|
|
and color(self.sibling) == 0
|
|
|
|
and color(self.sibling.right) == 1
|
|
|
|
):
|
|
|
|
self.parent.rotate_left()
|
|
|
|
self.grandparent.color = self.parent.color
|
|
|
|
self.parent.color = 0
|
|
|
|
self.parent.sibling.color = 0
|
|
|
|
if (
|
|
|
|
self.is_right()
|
|
|
|
and color(self.sibling) == 0
|
|
|
|
and color(self.sibling.left) == 1
|
|
|
|
):
|
|
|
|
self.parent.rotate_right()
|
|
|
|
self.grandparent.color = self.parent.color
|
|
|
|
self.parent.color = 0
|
|
|
|
self.parent.sibling.color = 0
|
|
|
|
|
2020-08-30 19:22:36 +00:00
|
|
|
def check_color_properties(self) -> bool:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Check the coloring of the tree, and return True iff the tree
|
|
|
|
is colored in a way which matches these five properties:
|
|
|
|
(wording stolen from wikipedia article)
|
|
|
|
1. Each node is either red or black.
|
|
|
|
2. The root node is black.
|
|
|
|
3. All leaves are black.
|
|
|
|
4. If a node is red, then both its children are black.
|
|
|
|
5. Every path from any node to all of its descendent NIL nodes
|
|
|
|
has the same number of black nodes.
|
|
|
|
This function runs in O(n) time, because properties 4 and 5 take
|
|
|
|
that long to check.
|
|
|
|
"""
|
|
|
|
# I assume property 1 to hold because there is nothing that can
|
|
|
|
# make the color be anything other than 0 or 1.
|
|
|
|
# Property 2
|
|
|
|
if self.color:
|
|
|
|
# The root was red
|
|
|
|
print("Property 2")
|
|
|
|
return False
|
|
|
|
# Property 3 does not need to be checked, because None is assumed
|
|
|
|
# to be black and is all the leaves.
|
|
|
|
# Property 4
|
|
|
|
if not self.check_coloring():
|
|
|
|
print("Property 4")
|
|
|
|
return False
|
|
|
|
# Property 5
|
|
|
|
if self.black_height() is None:
|
|
|
|
print("Property 5")
|
|
|
|
return False
|
|
|
|
# All properties were met
|
|
|
|
return True
|
|
|
|
|
2021-11-04 15:38:43 +00:00
|
|
|
def check_coloring(self) -> bool:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""A helper function to recursively check Property 4 of a
|
|
|
|
Red-Black Tree. See check_color_properties for more info.
|
|
|
|
"""
|
2023-03-01 16:23:33 +00:00
|
|
|
if self.color == 1 and 1 in (color(self.left), color(self.right)):
|
|
|
|
return False
|
2019-07-25 18:38:24 +00:00
|
|
|
if self.left and not self.left.check_coloring():
|
|
|
|
return False
|
|
|
|
if self.right and not self.right.check_coloring():
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
2021-11-04 15:38:43 +00:00
|
|
|
def black_height(self) -> int | None:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Returns the number of black nodes from this node to the
|
|
|
|
leaves of the tree, or None if there isn't one such value (the
|
|
|
|
tree is color incorrectly).
|
|
|
|
"""
|
2021-11-04 15:38:43 +00:00
|
|
|
if self is None or self.left is None or self.right is None:
|
2019-07-25 18:38:24 +00:00
|
|
|
# If we're already at a leaf, there is no path
|
|
|
|
return 1
|
|
|
|
left = RedBlackTree.black_height(self.left)
|
|
|
|
right = RedBlackTree.black_height(self.right)
|
|
|
|
if left is None or right is None:
|
|
|
|
# There are issues with coloring below children nodes
|
|
|
|
return None
|
|
|
|
if left != right:
|
|
|
|
# The two children have unequal depths
|
|
|
|
return None
|
|
|
|
# Return the black depth of children, plus one if this node is
|
|
|
|
# black
|
|
|
|
return left + (1 - self.color)
|
|
|
|
|
|
|
|
# Here are functions which are general to all binary search trees
|
|
|
|
|
2021-11-04 15:38:43 +00:00
|
|
|
def __contains__(self, label: int) -> bool:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Search through the tree for label, returning True iff it is
|
|
|
|
found somewhere in the tree.
|
|
|
|
Guaranteed to run in O(log(n)) time.
|
|
|
|
"""
|
|
|
|
return self.search(label) is not None
|
|
|
|
|
2021-11-04 15:38:43 +00:00
|
|
|
def search(self, label: int) -> RedBlackTree | None:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Search through the tree for label, returning its node if
|
|
|
|
it's found, and None otherwise.
|
|
|
|
This method is guaranteed to run in O(log(n)) time.
|
|
|
|
"""
|
|
|
|
if self.label == label:
|
|
|
|
return self
|
2021-11-04 15:38:43 +00:00
|
|
|
elif self.label is not None and label > self.label:
|
2019-07-25 18:38:24 +00:00
|
|
|
if self.right is None:
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
return self.right.search(label)
|
2024-03-28 17:25:41 +00:00
|
|
|
elif self.left is None:
|
|
|
|
return None
|
2019-07-25 18:38:24 +00:00
|
|
|
else:
|
2024-03-28 17:25:41 +00:00
|
|
|
return self.left.search(label)
|
2019-07-25 18:38:24 +00:00
|
|
|
|
2021-11-04 15:38:43 +00:00
|
|
|
def floor(self, label: int) -> int | None:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Returns the largest element in this tree which is at most label.
|
|
|
|
This method is guaranteed to run in O(log(n)) time."""
|
|
|
|
if self.label == label:
|
|
|
|
return self.label
|
2021-11-04 15:38:43 +00:00
|
|
|
elif self.label is not None and self.label > label:
|
2019-07-25 18:38:24 +00:00
|
|
|
if self.left:
|
|
|
|
return self.left.floor(label)
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
if self.right:
|
|
|
|
attempt = self.right.floor(label)
|
|
|
|
if attempt is not None:
|
|
|
|
return attempt
|
|
|
|
return self.label
|
|
|
|
|
2021-11-04 15:38:43 +00:00
|
|
|
def ceil(self, label: int) -> int | None:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Returns the smallest element in this tree which is at least label.
|
|
|
|
This method is guaranteed to run in O(log(n)) time.
|
|
|
|
"""
|
|
|
|
if self.label == label:
|
|
|
|
return self.label
|
2021-11-04 15:38:43 +00:00
|
|
|
elif self.label is not None and self.label < label:
|
2019-07-25 18:38:24 +00:00
|
|
|
if self.right:
|
|
|
|
return self.right.ceil(label)
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
if self.left:
|
|
|
|
attempt = self.left.ceil(label)
|
|
|
|
if attempt is not None:
|
|
|
|
return attempt
|
|
|
|
return self.label
|
|
|
|
|
2021-11-04 15:38:43 +00:00
|
|
|
def get_max(self) -> int | None:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Returns the largest element in this tree.
|
|
|
|
This method is guaranteed to run in O(log(n)) time.
|
|
|
|
"""
|
|
|
|
if self.right:
|
|
|
|
# Go as far right as possible
|
|
|
|
return self.right.get_max()
|
|
|
|
else:
|
|
|
|
return self.label
|
|
|
|
|
2021-11-04 15:38:43 +00:00
|
|
|
def get_min(self) -> int | None:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Returns the smallest element in this tree.
|
|
|
|
This method is guaranteed to run in O(log(n)) time.
|
|
|
|
"""
|
|
|
|
if self.left:
|
|
|
|
# Go as far left as possible
|
|
|
|
return self.left.get_min()
|
|
|
|
else:
|
|
|
|
return self.label
|
|
|
|
|
|
|
|
@property
|
2021-11-04 15:38:43 +00:00
|
|
|
def grandparent(self) -> RedBlackTree | None:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Get the current node's grandparent, or None if it doesn't exist."""
|
|
|
|
if self.parent is None:
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
return self.parent.parent
|
|
|
|
|
|
|
|
@property
|
2021-11-04 15:38:43 +00:00
|
|
|
def sibling(self) -> RedBlackTree | None:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Get the current node's sibling, or None if it doesn't exist."""
|
|
|
|
if self.parent is None:
|
|
|
|
return None
|
|
|
|
elif self.parent.left is self:
|
|
|
|
return self.parent.right
|
|
|
|
else:
|
|
|
|
return self.parent.left
|
|
|
|
|
2020-08-30 19:22:36 +00:00
|
|
|
def is_left(self) -> bool:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Returns true iff this node is the left child of its parent."""
|
2021-11-04 15:38:43 +00:00
|
|
|
if self.parent is None:
|
|
|
|
return False
|
2024-01-16 08:43:33 +00:00
|
|
|
return self.parent.left is self
|
2019-07-25 18:38:24 +00:00
|
|
|
|
2020-08-30 19:22:36 +00:00
|
|
|
def is_right(self) -> bool:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Returns true iff this node is the right child of its parent."""
|
2021-11-04 15:38:43 +00:00
|
|
|
if self.parent is None:
|
|
|
|
return False
|
|
|
|
return self.parent.right is self
|
2019-07-25 18:38:24 +00:00
|
|
|
|
2020-08-30 19:22:36 +00:00
|
|
|
def __bool__(self) -> bool:
|
2019-07-25 18:38:24 +00:00
|
|
|
return True
|
|
|
|
|
2020-08-30 19:22:36 +00:00
|
|
|
def __len__(self) -> int:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""
|
|
|
|
Return the number of nodes in this tree.
|
|
|
|
"""
|
|
|
|
ln = 1
|
|
|
|
if self.left:
|
|
|
|
ln += len(self.left)
|
|
|
|
if self.right:
|
|
|
|
ln += len(self.right)
|
|
|
|
return ln
|
|
|
|
|
2021-11-04 15:38:43 +00:00
|
|
|
def preorder_traverse(self) -> Iterator[int | None]:
|
2019-07-25 18:38:24 +00:00
|
|
|
yield self.label
|
|
|
|
if self.left:
|
|
|
|
yield from self.left.preorder_traverse()
|
|
|
|
if self.right:
|
|
|
|
yield from self.right.preorder_traverse()
|
|
|
|
|
2021-11-04 15:38:43 +00:00
|
|
|
def inorder_traverse(self) -> Iterator[int | None]:
|
2019-07-25 18:38:24 +00:00
|
|
|
if self.left:
|
|
|
|
yield from self.left.inorder_traverse()
|
|
|
|
yield self.label
|
|
|
|
if self.right:
|
|
|
|
yield from self.right.inorder_traverse()
|
|
|
|
|
2021-11-04 15:38:43 +00:00
|
|
|
def postorder_traverse(self) -> Iterator[int | None]:
|
2019-07-25 18:38:24 +00:00
|
|
|
if self.left:
|
|
|
|
yield from self.left.postorder_traverse()
|
|
|
|
if self.right:
|
|
|
|
yield from self.right.postorder_traverse()
|
|
|
|
yield self.label
|
|
|
|
|
2020-08-30 19:22:36 +00:00
|
|
|
def __repr__(self) -> str:
|
2019-07-25 18:38:24 +00:00
|
|
|
from pprint import pformat
|
|
|
|
|
|
|
|
if self.left is None and self.right is None:
|
2021-02-23 05:53:49 +00:00
|
|
|
return f"'{self.label} {(self.color and 'red') or 'blk'}'"
|
2019-07-25 18:38:24 +00:00
|
|
|
return pformat(
|
|
|
|
{
|
2021-02-23 05:53:49 +00:00
|
|
|
f"{self.label} {(self.color and 'red') or 'blk'}": (
|
|
|
|
self.left,
|
|
|
|
self.right,
|
|
|
|
)
|
2019-07-25 18:38:24 +00:00
|
|
|
},
|
|
|
|
indent=1,
|
|
|
|
)
|
|
|
|
|
2021-11-04 15:38:43 +00:00
|
|
|
def __eq__(self, other: object) -> bool:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Test if two trees are equal."""
|
2021-11-04 15:38:43 +00:00
|
|
|
if not isinstance(other, RedBlackTree):
|
|
|
|
return NotImplemented
|
2019-07-25 18:38:24 +00:00
|
|
|
if self.label == other.label:
|
|
|
|
return self.left == other.left and self.right == other.right
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
2021-11-04 15:38:43 +00:00
|
|
|
def color(node: RedBlackTree | None) -> int:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Returns the color of a node, allowing for None leaves."""
|
|
|
|
if node is None:
|
|
|
|
return 0
|
|
|
|
else:
|
|
|
|
return node.color
|
|
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
Code for testing the various
|
|
|
|
functions of the red-black tree.
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
2020-08-30 19:22:36 +00:00
|
|
|
def test_rotations() -> bool:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Test that the rotate_left and rotate_right functions work."""
|
|
|
|
# Make a tree to test on
|
|
|
|
tree = RedBlackTree(0)
|
|
|
|
tree.left = RedBlackTree(-10, parent=tree)
|
|
|
|
tree.right = RedBlackTree(10, parent=tree)
|
|
|
|
tree.left.left = RedBlackTree(-20, parent=tree.left)
|
|
|
|
tree.left.right = RedBlackTree(-5, parent=tree.left)
|
|
|
|
tree.right.left = RedBlackTree(5, parent=tree.right)
|
|
|
|
tree.right.right = RedBlackTree(20, parent=tree.right)
|
|
|
|
# Make the right rotation
|
|
|
|
left_rot = RedBlackTree(10)
|
|
|
|
left_rot.left = RedBlackTree(0, parent=left_rot)
|
|
|
|
left_rot.left.left = RedBlackTree(-10, parent=left_rot.left)
|
|
|
|
left_rot.left.right = RedBlackTree(5, parent=left_rot.left)
|
|
|
|
left_rot.left.left.left = RedBlackTree(-20, parent=left_rot.left.left)
|
|
|
|
left_rot.left.left.right = RedBlackTree(-5, parent=left_rot.left.left)
|
|
|
|
left_rot.right = RedBlackTree(20, parent=left_rot)
|
|
|
|
tree = tree.rotate_left()
|
|
|
|
if tree != left_rot:
|
|
|
|
return False
|
|
|
|
tree = tree.rotate_right()
|
|
|
|
tree = tree.rotate_right()
|
|
|
|
# Make the left rotation
|
|
|
|
right_rot = RedBlackTree(-10)
|
|
|
|
right_rot.left = RedBlackTree(-20, parent=right_rot)
|
|
|
|
right_rot.right = RedBlackTree(0, parent=right_rot)
|
|
|
|
right_rot.right.left = RedBlackTree(-5, parent=right_rot.right)
|
|
|
|
right_rot.right.right = RedBlackTree(10, parent=right_rot.right)
|
|
|
|
right_rot.right.right.left = RedBlackTree(5, parent=right_rot.right.right)
|
|
|
|
right_rot.right.right.right = RedBlackTree(20, parent=right_rot.right.right)
|
|
|
|
if tree != right_rot:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2020-08-30 19:22:36 +00:00
|
|
|
def test_insertion_speed() -> bool:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Test that the tree balances inserts to O(log(n)) by doing a lot
|
|
|
|
of them.
|
|
|
|
"""
|
|
|
|
tree = RedBlackTree(-1)
|
|
|
|
for i in range(300000):
|
|
|
|
tree = tree.insert(i)
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2020-08-30 19:22:36 +00:00
|
|
|
def test_insert() -> bool:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Test the insert() method of the tree correctly balances, colors,
|
|
|
|
and inserts.
|
|
|
|
"""
|
|
|
|
tree = RedBlackTree(0)
|
|
|
|
tree.insert(8)
|
|
|
|
tree.insert(-8)
|
|
|
|
tree.insert(4)
|
|
|
|
tree.insert(12)
|
|
|
|
tree.insert(10)
|
|
|
|
tree.insert(11)
|
|
|
|
ans = RedBlackTree(0, 0)
|
|
|
|
ans.left = RedBlackTree(-8, 0, ans)
|
|
|
|
ans.right = RedBlackTree(8, 1, ans)
|
|
|
|
ans.right.left = RedBlackTree(4, 0, ans.right)
|
|
|
|
ans.right.right = RedBlackTree(11, 0, ans.right)
|
|
|
|
ans.right.right.left = RedBlackTree(10, 1, ans.right.right)
|
|
|
|
ans.right.right.right = RedBlackTree(12, 1, ans.right.right)
|
|
|
|
return tree == ans
|
|
|
|
|
|
|
|
|
2020-08-30 19:22:36 +00:00
|
|
|
def test_insert_and_search() -> bool:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Tests searching through the tree for values."""
|
|
|
|
tree = RedBlackTree(0)
|
|
|
|
tree.insert(8)
|
|
|
|
tree.insert(-8)
|
|
|
|
tree.insert(4)
|
|
|
|
tree.insert(12)
|
|
|
|
tree.insert(10)
|
|
|
|
tree.insert(11)
|
|
|
|
if 5 in tree or -6 in tree or -10 in tree or 13 in tree:
|
|
|
|
# Found something not in there
|
|
|
|
return False
|
|
|
|
if not (11 in tree and 12 in tree and -8 in tree and 0 in tree):
|
|
|
|
# Didn't find something in there
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2020-08-30 19:22:36 +00:00
|
|
|
def test_insert_delete() -> bool:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Test the insert() and delete() method of the tree, verifying the
|
|
|
|
insertion and removal of elements, and the balancing of the tree.
|
|
|
|
"""
|
|
|
|
tree = RedBlackTree(0)
|
|
|
|
tree = tree.insert(-12)
|
|
|
|
tree = tree.insert(8)
|
|
|
|
tree = tree.insert(-8)
|
|
|
|
tree = tree.insert(15)
|
|
|
|
tree = tree.insert(4)
|
|
|
|
tree = tree.insert(12)
|
|
|
|
tree = tree.insert(10)
|
|
|
|
tree = tree.insert(9)
|
|
|
|
tree = tree.insert(11)
|
|
|
|
tree = tree.remove(15)
|
|
|
|
tree = tree.remove(-12)
|
|
|
|
tree = tree.remove(9)
|
|
|
|
if not tree.check_color_properties():
|
|
|
|
return False
|
|
|
|
if list(tree.inorder_traverse()) != [-8, 0, 4, 8, 10, 11, 12]:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2020-08-30 19:22:36 +00:00
|
|
|
def test_floor_ceil() -> bool:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Tests the floor and ceiling functions in the tree."""
|
|
|
|
tree = RedBlackTree(0)
|
|
|
|
tree.insert(-16)
|
|
|
|
tree.insert(16)
|
|
|
|
tree.insert(8)
|
|
|
|
tree.insert(24)
|
|
|
|
tree.insert(20)
|
|
|
|
tree.insert(22)
|
|
|
|
tuples = [(-20, None, -16), (-10, -16, 0), (8, 8, 8), (50, 24, None)]
|
|
|
|
for val, floor, ceil in tuples:
|
|
|
|
if tree.floor(val) != floor or tree.ceil(val) != ceil:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2020-08-30 19:22:36 +00:00
|
|
|
def test_min_max() -> bool:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Tests the min and max functions in the tree."""
|
|
|
|
tree = RedBlackTree(0)
|
|
|
|
tree.insert(-16)
|
|
|
|
tree.insert(16)
|
|
|
|
tree.insert(8)
|
|
|
|
tree.insert(24)
|
|
|
|
tree.insert(20)
|
|
|
|
tree.insert(22)
|
|
|
|
if tree.get_max() != 22 or tree.get_min() != -16:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2020-08-30 19:22:36 +00:00
|
|
|
def test_tree_traversal() -> bool:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""Tests the three different tree traversal functions."""
|
|
|
|
tree = RedBlackTree(0)
|
|
|
|
tree = tree.insert(-16)
|
|
|
|
tree.insert(16)
|
|
|
|
tree.insert(8)
|
|
|
|
tree.insert(24)
|
|
|
|
tree.insert(20)
|
|
|
|
tree.insert(22)
|
|
|
|
if list(tree.inorder_traverse()) != [-16, 0, 8, 16, 20, 22, 24]:
|
|
|
|
return False
|
|
|
|
if list(tree.preorder_traverse()) != [0, -16, 16, 8, 22, 20, 24]:
|
|
|
|
return False
|
|
|
|
if list(tree.postorder_traverse()) != [-16, 8, 20, 24, 22, 16, 0]:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2020-08-30 19:22:36 +00:00
|
|
|
def test_tree_chaining() -> bool:
|
2020-01-18 12:24:33 +00:00
|
|
|
"""Tests the three different tree chaining functions."""
|
2019-07-25 18:38:24 +00:00
|
|
|
tree = RedBlackTree(0)
|
|
|
|
tree = tree.insert(-16).insert(16).insert(8).insert(24).insert(20).insert(22)
|
|
|
|
if list(tree.inorder_traverse()) != [-16, 0, 8, 16, 20, 22, 24]:
|
|
|
|
return False
|
|
|
|
if list(tree.preorder_traverse()) != [0, -16, 16, 8, 22, 20, 24]:
|
|
|
|
return False
|
|
|
|
if list(tree.postorder_traverse()) != [-16, 8, 20, 24, 22, 16, 0]:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def print_results(msg: str, passes: bool) -> None:
|
|
|
|
print(str(msg), "works!" if passes else "doesn't work :(")
|
|
|
|
|
|
|
|
|
2020-08-30 19:22:36 +00:00
|
|
|
def pytests() -> None:
|
2019-07-25 18:38:24 +00:00
|
|
|
assert test_rotations()
|
|
|
|
assert test_insert()
|
|
|
|
assert test_insert_and_search()
|
|
|
|
assert test_insert_delete()
|
|
|
|
assert test_floor_ceil()
|
|
|
|
assert test_tree_traversal()
|
|
|
|
assert test_tree_chaining()
|
|
|
|
|
|
|
|
|
2020-08-30 19:22:36 +00:00
|
|
|
def main() -> None:
|
2019-07-25 18:38:24 +00:00
|
|
|
"""
|
|
|
|
>>> pytests()
|
|
|
|
"""
|
|
|
|
print_results("Rotating right and left", test_rotations())
|
|
|
|
print_results("Inserting", test_insert())
|
|
|
|
print_results("Searching", test_insert_and_search())
|
|
|
|
print_results("Deleting", test_insert_delete())
|
|
|
|
print_results("Floor and ceil", test_floor_ceil())
|
|
|
|
print_results("Tree traversal", test_tree_traversal())
|
|
|
|
print_results("Tree traversal", test_tree_chaining())
|
|
|
|
print("Testing tree balancing...")
|
|
|
|
print("This should only be a few seconds.")
|
|
|
|
test_insertion_speed()
|
|
|
|
print("Done!")
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|