You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

485 lines
15 KiB

5 years ago
//Based on js_es:
//
//https://github.com/vadimg/js_bintrees
//
//Copyright (C) 2011 by Vadim Graboys
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.
export class TreeBase {
_root;
size;
_comparator;
// removes all nodes from the tree
clear() {
this._root = null;
this.size = 0;
};
// returns node data if found, null otherwise
find(data) {
var res = this._root;
while (res !== null) {
var c = this._comparator(data, res.data);
if (c === 0) {
return res.data;
}
else {
res = res.get_child(c > 0);
}
}
return null;
};
// returns iterator to node if found, null otherwise
findIter = function (data) {
var res = this._root;
var iter = this.iterator();
while (res !== null) {
var c = this._comparator(data, res.data);
if (c === 0) {
iter._cursor = res;
return iter;
}
else {
iter._ancestors.push(res);
res = res.get_child(c > 0);
}
}
return null;
};
// Returns an interator to the tree node immediately before (or at) the element
lowerBound(data) {
return this._bound(data, this._comparator);
};
// Returns an interator to the tree node immediately after (or at) the element
upperBound(data) {
var cmp = this._comparator;
function reverse_cmp(a, b) {
return cmp(b, a);
}
return this._bound(data, reverse_cmp);
};
// returns null if tree is empty
min() {
var res = this._root;
if (res === null) {
return null;
}
while (res.left !== null) {
res = res.left;
}
return res.data;
};
// returns null if tree is empty
max() {
var res = this._root;
if (res === null) {
return null;
}
while (res.right !== null) {
res = res.right;
}
return res.data;
};
// returns a null iterator
// call next() or prev() to point to an element
iterator(): Iterator {
return new Iterator(this);
};
// calls cb on each node's data, in order
each(cb) {
var it = this.iterator(), data;
while ((data = it.next()) !== null) {
cb(data);
}
};
// calls cb on each node's data, in reverse order
reach(cb) {
var it = this.iterator(), data;
while ((data = it.prev()) !== null) {
cb(data);
}
};
// used for lowerBound and upperBound
_bound(data, cmp) {
var cur = this._root;
var iter = this.iterator();
while (cur !== null) {
var c = this._comparator(data, cur.data);
if (c === 0) {
iter._cursor = cur;
return iter;
}
iter._ancestors.push(cur);
cur = cur.get_child(c > 0);
}
for (var i = iter._ancestors.length - 1; i >= 0; --i) {
cur = iter._ancestors[i];
if (cmp(data, cur.data) > 0) {
iter._cursor = cur;
iter._ancestors.length = i;
return iter;
}
}
iter._ancestors.length = 0;
return iter;
};
}
export class Iterator {
_tree;
_ancestors;
_cursor;
constructor(tree) {
this._tree = tree;
this._ancestors = [];
this._cursor = null;
}
data() {
return this._cursor !== null ? this._cursor.data : null;
};
// if null-iterator, returns first node
// otherwise, returns next node
next() {
if (this._cursor === null) {
var root = this._tree._root;
if (root !== null) {
this._minNode(root);
}
}
else {
if (this._cursor.right === null) {
// no greater node in subtree, go up to parent
// if coming from a right child, continue up the stack
var save;
do {
save = this._cursor;
if (this._ancestors.length) {
this._cursor = this._ancestors.pop();
}
else {
this._cursor = null;
break;
}
} while (this._cursor.right === save);
}
else {
// get the next node from the subtree
this._ancestors.push(this._cursor);
this._minNode(this._cursor.right);
}
}
return this._cursor !== null ? this._cursor.data : null;
};
// if null-iterator, returns last node
// otherwise, returns previous node
prev() {
if (this._cursor === null) {
var root = this._tree._root;
if (root !== null) {
this._maxNode(root);
}
}
else {
if (this._cursor.left === null) {
var save;
do {
save = this._cursor;
if (this._ancestors.length) {
this._cursor = this._ancestors.pop();
}
else {
this._cursor = null;
break;
}
} while (this._cursor.left === save);
}
else {
this._ancestors.push(this._cursor);
this._maxNode(this._cursor.left);
}
}
return this._cursor !== null ? this._cursor.data : null;
};
_minNode(start) {
while (start.left !== null) {
this._ancestors.push(start);
start = start.left;
}
this._cursor = start;
};
_maxNode(start) {
while (start.right !== null) {
this._ancestors.push(start);
start = start.right;
}
this._cursor = start;
};
}
class Node {
data;
left;
right;
red;
constructor(data) {
this.data = data;
this.left = null;
this.right = null;
this.red = true;
}
get_child(dir) {
return dir ? this.right : this.left;
};
set_child(dir, val) {
if (dir) {
this.right = val;
}
else {
this.left = val;
}
};
}
export class RBTree<T> extends TreeBase {
_root;
_comparator;
size;
constructor(comparator: (a: T, b: T) => number) {
super();
this._root = null;
this._comparator = comparator;
this.size = 0;
}
// returns true if inserted, false if duplicate
insert(data) {
var ret = false;
if (this._root === null) {
// empty tree
this._root = new Node(data);
ret = true;
this.size++;
}
else {
var head = new Node(undefined); // fake tree root
var dir = false;
var last = false;
// setup
var gp = null; // grandparent
var ggp = head; // grand-grand-parent
var p = null; // parent
var node = this._root;
ggp.right = this._root;
// search down
while (true) {
if (node === null) {
// insert new node at the bottom
node = new Node(data);
p.set_child(dir, node);
ret = true;
this.size++;
}
else if (RBTree.is_red(node.left) && RBTree.is_red(node.right)) {
// color flip
node.red = true;
node.left.red = false;
node.right.red = false;
}
// fix red violation
if (RBTree.is_red(node) && RBTree.is_red(p)) {
var dir2 = ggp.right === gp;
if (node === p.get_child(last)) {
ggp.set_child(dir2, RBTree.single_rotate(gp, !last));
}
else {
ggp.set_child(dir2, RBTree.double_rotate(gp, !last));
}
}
var cmp = this._comparator(node.data, data);
// stop if found
if (cmp === 0) {
break;
}
last = dir;
dir = cmp < 0;
// update helpers
if (gp !== null) {
ggp = gp;
}
gp = p;
p = node;
node = node.get_child(dir);
}
// update root
this._root = head.right;
}
// make root black
this._root.red = false;
return ret;
};
// returns true if removed, false if not found
remove(data) {
if (this._root === null) {
return false;
}
var head = new Node(undefined); // fake tree root
var node = head;
node.right = this._root;
var p = null; // parent
var gp = null; // grand parent
var found = null; // found item
var dir = true;
while (node.get_child(dir) !== null) {
var last = dir;
// update helpers
gp = p;
p = node;
node = node.get_child(dir);
var cmp = this._comparator(data, node.data);
dir = cmp > 0;
// save found node
if (cmp === 0) {
found = node;
}
// push the red node down
if (!RBTree.is_red(node) && !RBTree.is_red(node.get_child(dir))) {
if (RBTree.is_red(node.get_child(!dir))) {
var sr = RBTree.single_rotate(node, dir);
p.set_child(last, sr);
p = sr;
}
else if (!RBTree.is_red(node.get_child(!dir))) {
var sibling = p.get_child(!last);
if (sibling !== null) {
if (!RBTree.is_red(sibling.get_child(!last)) && !RBTree.is_red(sibling.get_child(last))) {
// color flip
p.red = false;
sibling.red = true;
node.red = true;
}
else {
var dir2 = gp.right === p;
if (RBTree.is_red(sibling.get_child(last))) {
gp.set_child(dir2, RBTree.double_rotate(p, last));
}
else if (RBTree.is_red(sibling.get_child(!last))) {
gp.set_child(dir2, RBTree.single_rotate(p, last));
}
// ensure correct coloring
var gpc = gp.get_child(dir2);
gpc.red = true;
node.red = true;
gpc.left.red = false;
gpc.right.red = false;
}
}
}
}
}
// replace and remove if found
if (found !== null) {
found.data = node.data;
p.set_child(p.right === node, node.get_child(node.left === null));
this.size--;
}
// update root and make it black
this._root = head.right;
if (this._root !== null) {
this._root.red = false;
}
return found !== null;
};
static is_red(node) {
return node !== null && node.red;
}
static single_rotate(root, dir) {
var save = root.get_child(!dir);
root.set_child(!dir, save.get_child(dir));
save.set_child(dir, root);
root.red = true;
save.red = false;
return save;
}
static double_rotate(root, dir) {
root.set_child(!dir, RBTree.single_rotate(root.get_child(!dir), !dir));
return RBTree.single_rotate(root, dir);
}
}