///|
/// One operation accepted by the red-black-tree trace adapter.
pub(all) enum RedBlackTreeOperation {
Insert(Int)
Find(Int)
} derive(Debug, Eq, ToJson)
///|
/// Build a red-black-tree trace with insertion balancing and lookup paths.
pub fn red_black_tree_trace(
operations : Array[RedBlackTreeOperation],
title? : String = "红黑树",
object_id? : String = "tree",
label? : String = "红黑树结构",
options? : TraceOptions = TraceOptions::default(),
) -> AlgorithmTrace raise TraceError {
let mut requested_nodes = 0
for operation in operations {
if operation is Insert(_) {
requested_nodes += 1
}
}
if requested_nodes == 0 {
ensure_adapter_capacity(1, operations.length() + 1, options)
} else {
if requested_nodes > options.max_entities_per_scene / 2 {
raise LimitExceeded("Red-black tree exceeds the scene entity limit")
}
ensure_adapter_capacity(
requested_nodes * 2,
operations.length() + 1,
options,
)
}
let nodes : Array[RedBlackNode] = []
let mut root = -1
let stats = RedBlackTreeStats::new()
let builder = TraceBuilder::new(
title~,
algorithm="red-black-tree",
description="使用稳定节点 ID 展示红黑树的插入、平衡修复和查找过程。",
initial_scene=red_black_tree_scene(nodes, object_id, label, [], Result),
options~,
)
for operation in operations {
match operation {
Insert(key) => {
if red_black_tree_find_index(nodes, root, key) >= 0 {
raise InvalidStep("Duplicate red-black-tree key: \{key}")
}
let mut parent = -1
let mut current = root
while current >= 0 {
parent = current
current = if key < nodes[current].key {
nodes[current].left
} else {
nodes[current].right
}
}
let node_index = nodes.length()
nodes.push(
RedBlackNode::new(id="node-\{node_index}", key~, color=Red, parent~),
)
if parent < 0 {
root = node_index
} else if key < nodes[parent].key {
nodes[parent].left = node_index
} else {
nodes[parent].right = node_index
}
stats.insertions += 1
let insert_attributes = [
TraceAttribute::new(key="key", value=key.to_string()),
]
let insert_body = if parent < 0 {
"将键 \{key} 作为根节点插入,初始颜色为红色。"
} else {
let direction = if key < nodes[parent].key { "left" } else { "right" }
insert_attributes.push(
TraceAttribute::new(
key="parent",
value=nodes[parent].key.to_string(),
),
)
insert_attributes.push(
TraceAttribute::new(key="direction", value=direction),
)
if direction == "left" {
"将键 \{key} 作为节点 \{nodes[parent].key} 的左孩子插入,初始颜色为红色。"
} else {
"将键 \{key} 作为节点 \{nodes[parent].key} 的右孩子插入,初始颜色为红色。"
}
}
record_red_black_event(
builder,
nodes,
root,
object_id,
label,
kind="rb-insert",
attributes=insert_attributes,
highlighted=[node_index],
role=Changed,
title="插入红色节点",
body=insert_body,
)
root = red_black_tree_fix_insert(
builder, nodes, root, node_index, object_id, label, stats,
)
validate_red_black_tree(nodes, root)
}
Find(key) => {
stats.searches += 1
let mut current = root
let mut found = -1
while current >= 0 && found < 0 {
stats.visits += 1
builder.record(
event=Visit(TargetRef::entity(object_id, nodes[current].id)),
scene=red_black_tree_scene(
nodes,
object_id,
label,
[current],
Current,
),
annotation=Annotation::new(
title="查找键 \{key}",
body="将键 \{key} 与节点 \{nodes[current].key} 比较。",
),
)
if key == nodes[current].key {
found = current
} else if key < nodes[current].key {
current = nodes[current].left
} else {
current = nodes[current].right
}
}
if found >= 0 {
stats.found += 1
record_red_black_event(
builder,
nodes,
root,
object_id,
label,
kind="rb-find-hit",
attributes=[TraceAttribute::new(key="key", value=key.to_string())],
highlighted=[found],
role=Result,
title="查找命中",
body="树中存在键 \{key}。",
)
} else {
stats.missed += 1
record_red_black_event(
builder,
nodes,
root,
object_id,
label,
kind="rb-find-miss",
attributes=[TraceAttribute::new(key="key", value=key.to_string())],
highlighted=[],
role=Error,
title="查找未命中",
body="树中不存在键 \{key}。",
)
}
}
}
}
let all_nodes = Array::makei(nodes.length(), fn(index) { index })
builder.record(
event=Complete,
scene=red_black_tree_scene(nodes, object_id, label, all_nodes, Result),
annotation=Annotation::new(
title="红黑树操作完成",
body="所有插入和查找操作均已完成。",
),
)
let inorder : Array[String] = []
red_black_tree_inorder(nodes, root, inorder)
builder.finish(summary=[
TraceAttribute::new(key="operations", value=operations.length().to_string()),
TraceAttribute::new(key="insertions", value=stats.insertions.to_string()),
TraceAttribute::new(key="searches", value=stats.searches.to_string()),
TraceAttribute::new(key="found", value=stats.found.to_string()),
TraceAttribute::new(key="missed", value=stats.missed.to_string()),
TraceAttribute::new(key="visits", value=stats.visits.to_string()),
TraceAttribute::new(key="rotations", value=stats.rotations.to_string()),
TraceAttribute::new(key="recolors", value=stats.recolors.to_string()),
TraceAttribute::new(key="nodes", value=nodes.length().to_string()),
TraceAttribute::new(
key="height",
value=red_black_tree_height(nodes, root).to_string(),
),
TraceAttribute::new(
key="black_height",
value=red_black_tree_black_height(nodes, root).to_string(),
),
TraceAttribute::new(key="inorder", value=inorder.join(",")),
])
}
///|
priv enum RedBlackColor {
Red
Black
} derive(Eq)
///|
priv struct RedBlackNode {
id : String
key : Int
mut color : RedBlackColor
mut parent : Int
mut left : Int
mut right : Int
}
///|
fn RedBlackNode::new(
id~ : String,
key~ : Int,
color~ : RedBlackColor,
parent~ : Int,
) -> RedBlackNode {
{ id, key, color, parent, left: -1, right: -1 }
}
///|
priv struct RedBlackTreeStats {
mut insertions : Int
mut searches : Int
mut found : Int
mut missed : Int
mut visits : Int
mut rotations : Int
mut recolors : Int
}
///|
fn RedBlackTreeStats::new() -> RedBlackTreeStats {
{
insertions: 0,
searches: 0,
found: 0,
missed: 0,
visits: 0,
rotations: 0,
recolors: 0,
}
}
///|
fn red_black_tree_fix_insert(
builder : TraceBuilder,
nodes : Array[RedBlackNode],
initial_root : Int,
inserted : Int,
object_id : String,
label : String,
stats : RedBlackTreeStats,
) -> Int raise TraceError {
let mut root = initial_root
let mut current = inserted
while current != root && red_black_color(nodes, nodes[current].parent) == Red {
let parent = nodes[current].parent
let grand = nodes[parent].parent
if parent == nodes[grand].left {
let uncle = nodes[grand].right
if red_black_color(nodes, uncle) == Red {
nodes[parent].color = Black
nodes[uncle].color = Black
nodes[grand].color = Red
stats.recolors += 3
record_red_black_event(
builder,
nodes,
root,
object_id,
label,
kind="rb-recolor",
attributes=[
TraceAttribute::new(
key="grandparent",
value=nodes[grand].key.to_string(),
),
],
highlighted=[parent, uncle, grand],
role=Changed,
title="节点组重新着色",
body="父节点和叔节点变为黑色,祖父节点变为红色。",
)
current = grand
} else {
if current == nodes[parent].right {
current = parent
let pivot_key = nodes[current].key
root = red_black_rotate_left(nodes, root, current)
stats.rotations += 1
record_red_black_event(
builder,
nodes,
root,
object_id,
label,
kind="rb-rotate-left",
attributes=[
TraceAttribute::new(key="pivot", value=pivot_key.to_string()),
],
highlighted=[current, nodes[current].parent],
role=Changed,
title="左旋",
body="以节点 \{pivot_key} 为支点执行左旋。",
)
}
let fixed_parent = nodes[current].parent
let fixed_grand = nodes[fixed_parent].parent
nodes[fixed_parent].color = Black
nodes[fixed_grand].color = Red
stats.recolors += 2
record_red_black_event(
builder,
nodes,
root,
object_id,
label,
kind="rb-recolor",
attributes=[
TraceAttribute::new(
key="parent",
value=nodes[fixed_parent].key.to_string(),
),
],
highlighted=[fixed_parent, fixed_grand],
role=Changed,
title="准备右旋",
body="将父节点变为黑色、祖父节点变为红色。",
)
let pivot_key = nodes[fixed_grand].key
root = red_black_rotate_right(nodes, root, fixed_grand)
stats.rotations += 1
record_red_black_event(
builder,
nodes,
root,
object_id,
label,
kind="rb-rotate-right",
attributes=[
TraceAttribute::new(key="pivot", value=pivot_key.to_string()),
],
highlighted=[fixed_grand, nodes[fixed_grand].parent],
role=Changed,
title="右旋",
body="以节点 \{pivot_key} 为支点执行右旋。",
)
}
} else {
let uncle = nodes[grand].left
if red_black_color(nodes, uncle) == Red {
nodes[parent].color = Black
nodes[uncle].color = Black
nodes[grand].color = Red
stats.recolors += 3
record_red_black_event(
builder,
nodes,
root,
object_id,
label,
kind="rb-recolor",
attributes=[
TraceAttribute::new(
key="grandparent",
value=nodes[grand].key.to_string(),
),
],
highlighted=[parent, uncle, grand],
role=Changed,
title="节点组重新着色",
body="父节点和叔节点变为黑色,祖父节点变为红色。",
)
current = grand
} else {
if current == nodes[parent].left {
current = parent
let pivot_key = nodes[current].key
root = red_black_rotate_right(nodes, root, current)
stats.rotations += 1
record_red_black_event(
builder,
nodes,
root,
object_id,
label,
kind="rb-rotate-right",
attributes=[
TraceAttribute::new(key="pivot", value=pivot_key.to_string()),
],
highlighted=[current, nodes[current].parent],
role=Changed,
title="右旋",
body="以节点 \{pivot_key} 为支点执行右旋。",
)
}
let fixed_parent = nodes[current].parent
let fixed_grand = nodes[fixed_parent].parent
nodes[fixed_parent].color = Black
nodes[fixed_grand].color = Red
stats.recolors += 2
record_red_black_event(
builder,
nodes,
root,
object_id,
label,
kind="rb-recolor",
attributes=[
TraceAttribute::new(
key="parent",
value=nodes[fixed_parent].key.to_string(),
),
],
highlighted=[fixed_parent, fixed_grand],
role=Changed,
title="准备左旋",
body="将父节点变为黑色、祖父节点变为红色。",
)
let pivot_key = nodes[fixed_grand].key
root = red_black_rotate_left(nodes, root, fixed_grand)
stats.rotations += 1
record_red_black_event(
builder,
nodes,
root,
object_id,
label,
kind="rb-rotate-left",
attributes=[
TraceAttribute::new(key="pivot", value=pivot_key.to_string()),
],
highlighted=[fixed_grand, nodes[fixed_grand].parent],
role=Changed,
title="左旋",
body="以节点 \{pivot_key} 为支点执行左旋。",
)
}
}
}
if root >= 0 && nodes[root].color == Red {
nodes[root].color = Black
stats.recolors += 1
record_red_black_event(
builder,
nodes,
root,
object_id,
label,
kind="rb-recolor",
attributes=[
TraceAttribute::new(key="root", value=nodes[root].key.to_string()),
],
highlighted=[root],
role=Changed,
title="根节点染黑",
body="红黑树的根节点必须始终为黑色。",
)
}
root
}
///|
fn red_black_rotate_left(
nodes : Array[RedBlackNode],
initial_root : Int,
pivot : Int,
) -> Int {
let mut root = initial_root
let promoted = nodes[pivot].right
nodes[pivot].right = nodes[promoted].left
if nodes[promoted].left >= 0 {
nodes[nodes[promoted].left].parent = pivot
}
nodes[promoted].parent = nodes[pivot].parent
if nodes[pivot].parent < 0 {
root = promoted
} else if pivot == nodes[nodes[pivot].parent].left {
nodes[nodes[pivot].parent].left = promoted
} else {
nodes[nodes[pivot].parent].right = promoted
}
nodes[promoted].left = pivot
nodes[pivot].parent = promoted
root
}
///|
fn red_black_rotate_right(
nodes : Array[RedBlackNode],
initial_root : Int,
pivot : Int,
) -> Int {
let mut root = initial_root
let promoted = nodes[pivot].left
nodes[pivot].left = nodes[promoted].right
if nodes[promoted].right >= 0 {
nodes[nodes[promoted].right].parent = pivot
}
nodes[promoted].parent = nodes[pivot].parent
if nodes[pivot].parent < 0 {
root = promoted
} else if pivot == nodes[nodes[pivot].parent].right {
nodes[nodes[pivot].parent].right = promoted
} else {
nodes[nodes[pivot].parent].left = promoted
}
nodes[promoted].right = pivot
nodes[pivot].parent = promoted
root
}
///|
fn record_red_black_event(
builder : TraceBuilder,
nodes : Array[RedBlackNode],
root : Int,
object_id : String,
label : String,
kind~ : String,
attributes~ : Array[TraceAttribute],
highlighted~ : Array[Int],
role~ : HighlightRole,
title~ : String,
body~ : String,
) -> Unit raise TraceError {
ignore(root)
builder.record(
event=Custom(kind, attributes),
scene=red_black_tree_scene(nodes, object_id, label, highlighted, role),
annotation=Annotation::new(title~, body~),
)
}
///|
fn red_black_tree_scene(
nodes : Array[RedBlackNode],
object_id : String,
label : String,
highlighted : Array[Int],
role : HighlightRole,
) -> Scene raise TraceError {
let graph_nodes = nodes.map(fn(node) {
GraphNode::new(
id=node.id,
label="\{node.key} [\{if node.color == Red { "R" } else { "B" }}]",
)
})
let edges : Array[GraphEdge] = []
for node in nodes {
if node.left >= 0 {
let child = nodes[node.left]
edges.push(
GraphEdge::new(
id="edge-\{node.id}-\{child.id}",
from=node.id,
to=child.id,
label="left",
directed=true,
),
)
}
if node.right >= 0 {
let child = nodes[node.right]
edges.push(
GraphEdge::new(
id="edge-\{node.id}-\{child.id}",
from=node.id,
to=child.id,
label="right",
directed=true,
),
)
}
}
Scene::new(
objects=[
Graph(GraphState::new(id=object_id, label~, nodes=graph_nodes, edges~)),
],
highlights=highlighted.map(fn(index) {
Highlight::new(
target=TargetRef::entity(object_id, nodes[index].id),
role~,
)
}),
)
}
///|
fn red_black_color(nodes : Array[RedBlackNode], index : Int) -> RedBlackColor {
if index < 0 {
Black
} else {
nodes[index].color
}
}
///|
fn red_black_tree_find_index(
nodes : Array[RedBlackNode],
root : Int,
key : Int,
) -> Int {
for current = root; current >= 0; {
if key == nodes[current].key {
break current
} else if key < nodes[current].key {
continue nodes[current].left
} else {
continue nodes[current].right
}
} nobreak {
-1
}
}
///|
fn validate_red_black_tree(
nodes : Array[RedBlackNode],
root : Int,
) -> Unit raise TraceError {
if nodes.is_empty() {
if root != -1 {
raise InvalidStep("Empty red-black tree has a root")
}
return
}
if root < 0 || root >= nodes.length() {
raise InvalidStep("Red-black-tree root is invalid")
}
if nodes[root].parent != -1 {
raise InvalidStep("Red-black-tree root must not have a parent")
}
if nodes[root].color != Black {
raise InvalidStep("Red-black-tree root must be black")
}
let seen = Array::make(nodes.length(), false)
ignore(validate_red_black_subtree(nodes, root, -1, None, None, seen))
if seen.any(fn(value) { !value }) {
raise InvalidStep("Red-black tree contains a disconnected node")
}
}
///|
fn validate_red_black_subtree(
nodes : Array[RedBlackNode],
index : Int,
parent : Int,
minimum : Int?,
maximum : Int?,
seen : Array[Bool],
) -> Int raise TraceError {
if index < 0 {
return 1
}
if index >= nodes.length() || seen[index] {
raise InvalidStep("Red-black tree contains a cycle or invalid child")
}
seen[index] = true
let node = nodes[index]
if node.parent != parent {
raise InvalidStep("Red-black-tree parent link is inconsistent")
}
match minimum {
Some(bound) if node.key <= bound =>
raise InvalidStep("Red-black tree violates BST ordering")
_ => ()
}
match maximum {
Some(bound) if node.key >= bound =>
raise InvalidStep("Red-black tree violates BST ordering")
_ => ()
}
if node.color == Red &&
(
red_black_color(nodes, node.left) == Red ||
red_black_color(nodes, node.right) == Red
) {
raise InvalidStep("Red node has a red child")
}
let left_black_height = validate_red_black_subtree(
nodes,
node.left,
index,
minimum,
Some(node.key),
seen,
)
let right_black_height = validate_red_black_subtree(
nodes,
node.right,
index,
Some(node.key),
maximum,
seen,
)
if left_black_height != right_black_height {
raise InvalidStep("Red-black tree has inconsistent black height")
}
left_black_height + (if node.color == Black { 1 } else { 0 })
}
///|
fn red_black_tree_height(nodes : Array[RedBlackNode], root : Int) -> Int {
if root < 0 {
0
} else {
1 +
red_black_tree_height(nodes, nodes[root].left).max(
red_black_tree_height(nodes, nodes[root].right),
)
}
}
///|
fn red_black_tree_black_height(nodes : Array[RedBlackNode], root : Int) -> Int {
if root < 0 {
0
} else {
(if nodes[root].color == Black { 1 } else { 0 }) +
red_black_tree_black_height(nodes, nodes[root].left)
}
}
///|
fn red_black_tree_inorder(
nodes : Array[RedBlackNode],
root : Int,
values : Array[String],
) -> Unit {
if root >= 0 {
red_black_tree_inorder(nodes, nodes[root].left, values)
values.push(nodes[root].key.to_string())
red_black_tree_inorder(nodes, nodes[root].right, values)
}
}