2019-10-26 18:29:32 -04:00
|
|
|
// Derived from https://github.com/vadimg/js_bintrees. MIT Licensed.
|
|
|
|
|
2020-03-28 13:03:49 -04:00
|
|
|
import { assert } from "./util.ts";
|
|
|
|
|
2019-10-26 18:29:32 -04:00
|
|
|
class RBNode<T> {
|
|
|
|
public left: this | null;
|
|
|
|
public right: this | null;
|
|
|
|
public red: boolean;
|
|
|
|
|
|
|
|
constructor(public data: T) {
|
|
|
|
this.left = null;
|
|
|
|
this.right = null;
|
|
|
|
this.red = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
getChild(dir: boolean | number): this | null {
|
|
|
|
return dir ? this.right : this.left;
|
|
|
|
}
|
|
|
|
|
|
|
|
setChild(dir: boolean | number, val: this | null): void {
|
|
|
|
if (dir) {
|
|
|
|
this.right = val;
|
|
|
|
} else {
|
|
|
|
this.left = val;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-28 13:03:49 -04:00
|
|
|
export class RBTree<T> {
|
|
|
|
#comparator: (a: T, b: T) => number;
|
|
|
|
#root: RBNode<T> | null;
|
2019-10-26 18:29:32 -04:00
|
|
|
|
2020-03-28 13:03:49 -04:00
|
|
|
constructor(comparator: (a: T, b: T) => number) {
|
|
|
|
this.#comparator = comparator;
|
|
|
|
this.#root = null;
|
2019-10-26 18:29:32 -04:00
|
|
|
}
|
|
|
|
|
2020-03-28 13:03:49 -04:00
|
|
|
/** Returns `null` if tree is empty. */
|
2019-10-26 18:29:32 -04:00
|
|
|
min(): T | null {
|
2020-03-28 13:03:49 -04:00
|
|
|
let res = this.#root;
|
2019-10-26 18:29:32 -04:00
|
|
|
if (res === null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
while (res.left !== null) {
|
|
|
|
res = res.left;
|
|
|
|
}
|
|
|
|
return res.data;
|
|
|
|
}
|
|
|
|
|
2020-03-28 13:03:49 -04:00
|
|
|
/** Returns node `data` if found, `null` otherwise. */
|
2019-10-26 18:29:32 -04:00
|
|
|
find(data: T): T | null {
|
2020-03-28 13:03:49 -04:00
|
|
|
let res = this.#root;
|
2019-10-26 18:29:32 -04:00
|
|
|
while (res !== null) {
|
2020-03-28 13:03:49 -04:00
|
|
|
const c = this.#comparator(data, res.data);
|
2019-10-26 18:29:32 -04:00
|
|
|
if (c === 0) {
|
|
|
|
return res.data;
|
|
|
|
} else {
|
|
|
|
res = res.getChild(c > 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2020-03-28 13:03:49 -04:00
|
|
|
/** returns `true` if inserted, `false` if duplicate. */
|
2019-10-26 18:29:32 -04:00
|
|
|
insert(data: T): boolean {
|
|
|
|
let ret = false;
|
|
|
|
|
2020-03-28 13:03:49 -04:00
|
|
|
if (this.#root === null) {
|
2019-10-26 18:29:32 -04:00
|
|
|
// empty tree
|
2020-03-28 13:03:49 -04:00
|
|
|
this.#root = new RBNode(data);
|
2019-10-26 18:29:32 -04:00
|
|
|
ret = true;
|
|
|
|
} else {
|
|
|
|
const head = new RBNode((null as unknown) as T); // fake tree root
|
|
|
|
|
|
|
|
let dir = 0;
|
|
|
|
let last = 0;
|
|
|
|
|
|
|
|
// setup
|
|
|
|
let gp = null; // grandparent
|
|
|
|
let ggp = head; // grand-grand-parent
|
|
|
|
let p: RBNode<T> | null = null; // parent
|
2020-03-28 13:03:49 -04:00
|
|
|
let node: RBNode<T> | null = this.#root;
|
|
|
|
ggp.right = this.#root;
|
2019-10-26 18:29:32 -04:00
|
|
|
|
|
|
|
// search down
|
|
|
|
while (true) {
|
|
|
|
if (node === null) {
|
|
|
|
// insert new node at the bottom
|
|
|
|
node = new RBNode(data);
|
|
|
|
p!.setChild(dir, node);
|
|
|
|
ret = true;
|
|
|
|
} else if (isRed(node.left) && isRed(node.right)) {
|
|
|
|
// color flip
|
|
|
|
node.red = true;
|
|
|
|
node.left!.red = false;
|
|
|
|
node.right!.red = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// fix red violation
|
|
|
|
if (isRed(node) && isRed(p)) {
|
|
|
|
const dir2 = ggp.right === gp;
|
|
|
|
|
2020-03-28 13:03:49 -04:00
|
|
|
assert(gp);
|
2019-10-26 18:29:32 -04:00
|
|
|
if (node === p!.getChild(last)) {
|
2020-03-28 13:03:49 -04:00
|
|
|
ggp.setChild(dir2, singleRotate(gp, !last));
|
2019-10-26 18:29:32 -04:00
|
|
|
} else {
|
2020-03-28 13:03:49 -04:00
|
|
|
ggp.setChild(dir2, doubleRotate(gp, !last));
|
2019-10-26 18:29:32 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-28 13:03:49 -04:00
|
|
|
const cmp = this.#comparator(node.data, data);
|
2019-10-26 18:29:32 -04:00
|
|
|
|
|
|
|
// stop if found
|
|
|
|
if (cmp === 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
last = dir;
|
|
|
|
dir = Number(cmp < 0); // Fix type
|
|
|
|
|
|
|
|
// update helpers
|
|
|
|
if (gp !== null) {
|
|
|
|
ggp = gp;
|
|
|
|
}
|
|
|
|
gp = p;
|
|
|
|
p = node;
|
|
|
|
node = node.getChild(dir);
|
|
|
|
}
|
|
|
|
|
|
|
|
// update root
|
2020-03-28 13:03:49 -04:00
|
|
|
this.#root = head.right;
|
2019-10-26 18:29:32 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// make root black
|
2020-03-28 13:03:49 -04:00
|
|
|
this.#root!.red = false;
|
2019-10-26 18:29:32 -04:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2020-03-28 13:03:49 -04:00
|
|
|
/** Returns `true` if removed, `false` if not found. */
|
2019-10-26 18:29:32 -04:00
|
|
|
remove(data: T): boolean {
|
2020-03-28 13:03:49 -04:00
|
|
|
if (this.#root === null) {
|
2019-10-26 18:29:32 -04:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const head = new RBNode((null as unknown) as T); // fake tree root
|
|
|
|
let node = head;
|
2020-03-28 13:03:49 -04:00
|
|
|
node.right = this.#root;
|
2019-10-26 18:29:32 -04:00
|
|
|
let p = null; // parent
|
|
|
|
let gp = null; // grand parent
|
|
|
|
let found = null; // found item
|
|
|
|
let dir: boolean | number = 1;
|
|
|
|
|
|
|
|
while (node.getChild(dir) !== null) {
|
|
|
|
const last = dir;
|
|
|
|
|
|
|
|
// update helpers
|
|
|
|
gp = p;
|
|
|
|
p = node;
|
|
|
|
node = node.getChild(dir)!;
|
|
|
|
|
2020-03-28 13:03:49 -04:00
|
|
|
const cmp = this.#comparator(data, node.data);
|
2019-10-26 18:29:32 -04:00
|
|
|
|
|
|
|
dir = cmp > 0;
|
|
|
|
|
|
|
|
// save found node
|
|
|
|
if (cmp === 0) {
|
|
|
|
found = node;
|
|
|
|
}
|
|
|
|
|
|
|
|
// push the red node down
|
|
|
|
if (!isRed(node) && !isRed(node.getChild(dir))) {
|
|
|
|
if (isRed(node.getChild(!dir))) {
|
|
|
|
const sr = singleRotate(node, dir);
|
|
|
|
p.setChild(last, sr);
|
|
|
|
p = sr;
|
|
|
|
} else if (!isRed(node.getChild(!dir))) {
|
|
|
|
const sibling = p.getChild(!last);
|
|
|
|
if (sibling !== null) {
|
|
|
|
if (
|
|
|
|
!isRed(sibling.getChild(!last)) &&
|
|
|
|
!isRed(sibling.getChild(last))
|
|
|
|
) {
|
|
|
|
// color flip
|
|
|
|
p.red = false;
|
|
|
|
sibling.red = true;
|
|
|
|
node.red = true;
|
|
|
|
} else {
|
2020-03-28 13:03:49 -04:00
|
|
|
assert(gp);
|
|
|
|
const dir2 = gp.right === p;
|
2019-10-26 18:29:32 -04:00
|
|
|
|
|
|
|
if (isRed(sibling.getChild(last))) {
|
|
|
|
gp!.setChild(dir2, doubleRotate(p, last));
|
|
|
|
} else if (isRed(sibling.getChild(!last))) {
|
|
|
|
gp!.setChild(dir2, singleRotate(p, last));
|
|
|
|
}
|
|
|
|
|
|
|
|
// ensure correct coloring
|
2020-03-28 13:03:49 -04:00
|
|
|
const gpc = gp.getChild(dir2);
|
|
|
|
assert(gpc);
|
2019-10-26 18:29:32 -04:00
|
|
|
gpc.red = true;
|
|
|
|
node.red = true;
|
2020-03-28 13:03:49 -04:00
|
|
|
assert(gpc.left);
|
|
|
|
gpc.left.red = false;
|
|
|
|
assert(gpc.right);
|
|
|
|
gpc.right.red = false;
|
2019-10-26 18:29:32 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// replace and remove if found
|
|
|
|
if (found !== null) {
|
|
|
|
found.data = node.data;
|
2020-03-28 13:03:49 -04:00
|
|
|
assert(p);
|
|
|
|
p.setChild(p.right === node, node.getChild(node.left === null));
|
2019-10-26 18:29:32 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// update root and make it black
|
2020-03-28 13:03:49 -04:00
|
|
|
this.#root = head.right;
|
|
|
|
if (this.#root !== null) {
|
|
|
|
this.#root.red = false;
|
2019-10-26 18:29:32 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return found !== null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function isRed<T>(node: RBNode<T> | null): boolean {
|
|
|
|
return node !== null && node.red;
|
|
|
|
}
|
|
|
|
|
|
|
|
function singleRotate<T>(root: RBNode<T>, dir: boolean | number): RBNode<T> {
|
2020-03-28 13:03:49 -04:00
|
|
|
const save = root.getChild(!dir);
|
|
|
|
assert(save);
|
2019-10-26 18:29:32 -04:00
|
|
|
|
|
|
|
root.setChild(!dir, save.getChild(dir));
|
|
|
|
save.setChild(dir, root);
|
|
|
|
|
|
|
|
root.red = true;
|
|
|
|
save.red = false;
|
|
|
|
|
|
|
|
return save;
|
|
|
|
}
|
|
|
|
|
|
|
|
function doubleRotate<T>(root: RBNode<T>, dir: boolean | number): RBNode<T> {
|
|
|
|
root.setChild(!dir, singleRotate(root.getChild(!dir)!, !dir));
|
|
|
|
return singleRotate(root, dir);
|
|
|
|
}
|