feat(rbt): implement red-black tree

Signed-off-by: Matej Focko <mfocko@redhat.com>
This commit is contained in:
Matej Focko 2022-05-20 15:30:15 +02:00
parent df787476ac
commit dce3e3c11c
Signed by: mfocko
GPG key ID: 7C47D46246790496
3 changed files with 199 additions and 0 deletions

View file

@ -148,6 +148,18 @@
<label class="btn btn-outline-primary" for="lRavlTreeBtn"
>rAVL</label
>
<input
type="radio"
class="btn-check"
name="leftTreeSelection"
id="lRbTreeBtn"
autocomplete="off"
onclick="switchTree(RBTree, 'left')"
/>
<label class="btn btn-outline-primary" for="lRbTreeBtn"
>Red-Black</label
>
</div>
</div>
</div>
@ -190,6 +202,18 @@
<label class="btn btn-outline-primary" for="rRavlTreeBtn"
>rAVL</label
>
<input
type="radio"
class="btn-check"
name="rightTreeSelection"
id="rRbTreeBtn"
autocomplete="off"
onclick="switchTree(RBTree, 'right')"
/>
<label class="btn btn-outline-primary" for="rRbTreeBtn"
>Red-Black</label
>
</div>
</div>
</div>
@ -211,6 +235,7 @@
<script src="avl.js"></script>
<script src="wavl.js"></script>
<script src="ravl.js"></script>
<script src="rbt.js"></script>
<script src="recorder.js"></script>

View file

@ -115,6 +115,18 @@
<label class="btn btn-outline-primary" for="ravlTreeBtn"
>rAVL</label
>
<input
type="radio"
class="btn-check"
name="treeSelection"
id="rbTreeBtn"
autocomplete="off"
onclick="switchTree(RBTree)"
/>
<label class="btn btn-outline-primary" for="rbTreeBtn"
>Red-Black</label
>
</div>
</div>
</div>
@ -186,6 +198,7 @@
<script src="avl.js"></script>
<script src="wavl.js"></script>
<script src="ravl.js"></script>
<script src="rbt.js"></script>
<script src="recorder.js"></script>

161
rbt.js Normal file
View file

@ -0,0 +1,161 @@
const RED = 0;
const BLACK = 1;
function isDoubleBlack(x) {
return x && nodeDifferences(x).indexOf(2) != -1;
}
class RBTree extends RankedTree {
isCorrectNode(node, recursive) {
if (!node) {
return true;
}
let [left, right] = nodeDifferences(node);
if ([RED, BLACK].indexOf(left) == -1) {
// left subtree has invalid difference
return false;
} else if ([RED, BLACK].indexOf(right) == -1) {
// right subtree has invalid difference
return false;
}
if (nodeDifference(node) == RED && (left == RED || right == RED)) {
// two consecutive red nodes
return false;
}
recursive = recursive ?? true;
return (
!recursive ||
(this.isCorrectNode(node.left) && this.isCorrectNode(node.right))
);
}
insertRebalanceStep(z, y, rightChild, rotateLeft, rotateRight) {
let [p, pp] = [z.parent, z.parent.parent];
if (y && nodeDifference(y) == RED) {
// Case 1
// ======
// zs uncle y is red
pp.rank++;
this.record("Insertion case #1: recoloring uncle", pp);
z = pp;
} else if (z === rightChild) {
// Case 2
// ======
// zs uncle y is black and z is a right child
z = p;
rotateLeft(p, this);
this.record("Insertion case #2: rotating by parent");
} else {
// Case 3
// ======
// zs uncle y is black and z is a left child
rotateRight(pp, this);
this.record("Insertion case #3: rotating by grandparent");
}
return z;
}
insertRebalance(z) {
while (z.parent && nodeDifference(z.parent) == RED) {
let [p, pp] = [z.parent, z.parent.parent];
if (p === pp.left) {
z = this.insertRebalanceStep(
z,
pp.right,
p.right,
rotateLeft,
rotateRight
);
} else {
z = this.insertRebalanceStep(
z,
pp.left,
p.left,
rotateRight,
rotateLeft
);
}
}
}
deleteRebalanceStep(x, w, parent, right, rotateLeft, rotateRight) {
if (nodeDifference(w) == RED) {
// Case 1
// ======
// xs sibling w is red
rotateLeft(parent, this);
this.record("Deletion case #1: rotating by parent", parent);
w = right(parent);
}
if (nodeDifferences(w).equals([BLACK, BLACK])) {
// Case 2
// ======
// xs sibling w is black, and both of ws children are black
parent.rank--;
this.record("Deletion case #2: recoloring sibling", w);
x = parent;
} else {
// Case 3
// ======
// xs sibling w is black,
// ws left child is red, and ws right child is black
if (nodeDifference(right(w), w) == BLACK) {
rotateRight(w, this);
this.record("Deletion case #3: rotating by w", w);
w = right(parent);
}
// Case 4
// ======
// xs sibling w is black, and ws right child is red
parent.rank--;
this.record(
"Deletion case #4: moving double black to parent",
parent
);
w.rank++;
this.record("Deletion case #4: coloring w's child", w);
rotateLeft(parent, this);
this.record("Deletion case #4: rotating by parent", parent);
x = this.root;
}
return [x, x ? x.parent : null];
}
deleteRebalance(node, parent) {
if (!node && !parent) {
return;
}
while (node !== this.root && isDoubleBlack(parent)) {
if (node === parent.left) {
[node, parent] = this.deleteRebalanceStep(
node,
parent.right,
parent,
(x) => x.right,
rotateLeft,
rotateRight
);
} else {
[node, parent] = this.deleteRebalanceStep(
node,
parent.left,
parent,
(x) => x.left,
rotateRight,
rotateLeft
);
}
}
}
}