diff --git a/comparator.html b/comparator.html
index 1d51e0c..ab75b8e 100755
--- a/comparator.html
+++ b/comparator.html
@@ -148,6 +148,18 @@
+
+
+
@@ -190,6 +202,18 @@
+
+
+
@@ -211,6 +235,7 @@
+
diff --git a/index.html b/index.html
index f253e91..20f1f5a 100755
--- a/index.html
+++ b/index.html
@@ -115,6 +115,18 @@
+
+
+
@@ -186,6 +198,7 @@
+
diff --git a/rbt.js b/rbt.js
new file mode 100644
index 0000000..ed659dc
--- /dev/null
+++ b/rbt.js
@@ -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
+ // ======
+ // z’s uncle y is red
+ pp.rank++;
+ this.record("Insertion case #1: recoloring uncle", pp);
+ z = pp;
+ } else if (z === rightChild) {
+ // Case 2
+ // ======
+ // z’s 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
+ // ======
+ // z’s 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
+ // ======
+ // x’s 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
+ // ======
+ // x’s sibling w is black, and both of w’s children are black
+ parent.rank--;
+ this.record("Deletion case #2: recoloring sibling", w);
+ x = parent;
+ } else {
+ // Case 3
+ // ======
+ // x’s sibling w is black,
+ // w’s left child is red, and w’s 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
+ // ======
+ // x’s sibling w is black, and w’s 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
+ );
+ }
+ }
+ }
+}