607 lines
13 KiB
JavaScript
607 lines
13 KiB
JavaScript
function DEFAULT_COMPARE (a, b) { return a > b ? 1 : a < b ? -1 : 0; }
|
|
|
|
export default class SplayTree {
|
|
|
|
constructor(compare = DEFAULT_COMPARE, noDuplicates = false) {
|
|
this._compare = compare;
|
|
this._root = null;
|
|
this._size = 0;
|
|
this._noDuplicates = !!noDuplicates;
|
|
}
|
|
|
|
|
|
rotateLeft(x) {
|
|
var y = x.right;
|
|
if (y) {
|
|
x.right = y.left;
|
|
if (y.left) y.left.parent = x;
|
|
y.parent = x.parent;
|
|
}
|
|
|
|
if (!x.parent) this._root = y;
|
|
else if (x === x.parent.left) x.parent.left = y;
|
|
else x.parent.right = y;
|
|
if (y) y.left = x;
|
|
x.parent = y;
|
|
}
|
|
|
|
|
|
rotateRight(x) {
|
|
var y = x.left;
|
|
if (y) {
|
|
x.left = y.right;
|
|
if (y.right) y.right.parent = x;
|
|
y.parent = x.parent;
|
|
}
|
|
|
|
if (!x.parent) this._root = y;
|
|
else if(x === x.parent.left) x.parent.left = y;
|
|
else x.parent.right = y;
|
|
if (y) y.right = x;
|
|
x.parent = y;
|
|
}
|
|
|
|
|
|
_splay(x) {
|
|
while (x.parent) {
|
|
var p = x.parent;
|
|
if (!p.parent) {
|
|
if (p.left === x) this.rotateRight(p);
|
|
else this.rotateLeft(p);
|
|
} else if (p.left === x && p.parent.left === p) {
|
|
this.rotateRight(p.parent);
|
|
this.rotateRight(p);
|
|
} else if (p.right === x && p.parent.right === p) {
|
|
this.rotateLeft(p.parent);
|
|
this.rotateLeft(p);
|
|
} else if (p.left === x && p.parent.right === p) {
|
|
this.rotateRight(p);
|
|
this.rotateLeft(p);
|
|
} else {
|
|
this.rotateLeft(p);
|
|
this.rotateRight(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
splay(x) {
|
|
var p, gp, ggp, l, r;
|
|
|
|
while (x.parent) {
|
|
p = x.parent;
|
|
gp = p.parent;
|
|
|
|
if (gp && gp.parent) {
|
|
ggp = gp.parent;
|
|
if (ggp.left === gp) ggp.left = x;
|
|
else ggp.right = x;
|
|
x.parent = ggp;
|
|
} else {
|
|
x.parent = null;
|
|
this._root = x;
|
|
}
|
|
|
|
l = x.left; r = x.right;
|
|
|
|
if (x === p.left) { // left
|
|
if (gp) {
|
|
if (gp.left === p) {
|
|
/* zig-zig */
|
|
if (p.right) {
|
|
gp.left = p.right;
|
|
gp.left.parent = gp;
|
|
} else gp.left = null;
|
|
|
|
p.right = gp;
|
|
gp.parent = p;
|
|
} else {
|
|
/* zig-zag */
|
|
if (l) {
|
|
gp.right = l;
|
|
l.parent = gp;
|
|
} else gp.right = null;
|
|
|
|
x.left = gp;
|
|
gp.parent = x;
|
|
}
|
|
}
|
|
if (r) {
|
|
p.left = r;
|
|
r.parent = p;
|
|
} else p.left = null;
|
|
|
|
x.right = p;
|
|
p.parent = x;
|
|
} else { // right
|
|
if (gp) {
|
|
if (gp.right === p) {
|
|
/* zig-zig */
|
|
if (p.left) {
|
|
gp.right = p.left;
|
|
gp.right.parent = gp;
|
|
} else gp.right = null;
|
|
|
|
p.left = gp;
|
|
gp.parent = p;
|
|
} else {
|
|
/* zig-zag */
|
|
if (r) {
|
|
gp.left = r;
|
|
r.parent = gp;
|
|
} else gp.left = null;
|
|
|
|
x.right = gp;
|
|
gp.parent = x;
|
|
}
|
|
}
|
|
if (l) {
|
|
p.right = l;
|
|
l.parent = p;
|
|
} else p.right = null;
|
|
|
|
x.left = p;
|
|
p.parent = x;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
replace(u, v) {
|
|
if (!u.parent) this._root = v;
|
|
else if (u === u.parent.left) u.parent.left = v;
|
|
else u.parent.right = v;
|
|
if (v) v.parent = u.parent;
|
|
}
|
|
|
|
|
|
minNode(u = this._root) {
|
|
if (u) while (u.left) u = u.left;
|
|
return u;
|
|
}
|
|
|
|
|
|
maxNode(u = this._root) {
|
|
if (u) while (u.right) u = u.right;
|
|
return u;
|
|
}
|
|
|
|
|
|
insert(key, data) {
|
|
var z = this._root;
|
|
var p = null;
|
|
var comp = this._compare;
|
|
var cmp;
|
|
|
|
if (this._noDuplicates) {
|
|
while (z) {
|
|
p = z;
|
|
cmp = comp(z.key, key);
|
|
if (cmp === 0) return;
|
|
else if (comp(z.key, key) < 0) z = z.right;
|
|
else z = z.left;
|
|
}
|
|
} else {
|
|
while (z) {
|
|
p = z;
|
|
if (comp(z.key, key) < 0) z = z.right;
|
|
else z = z.left;
|
|
}
|
|
}
|
|
|
|
z = { key, data, left: null, right: null, parent: p };
|
|
|
|
if (!p) this._root = z;
|
|
else if (comp(p.key, z.key) < 0) p.right = z;
|
|
else p.left = z;
|
|
|
|
this.splay(z);
|
|
this._size++;
|
|
return z;
|
|
}
|
|
|
|
|
|
find (key) {
|
|
var z = this._root;
|
|
var comp = this._compare;
|
|
while (z) {
|
|
var cmp = comp(z.key, key);
|
|
if (cmp < 0) z = z.right;
|
|
else if (cmp > 0) z = z.left;
|
|
else return z;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Whether the tree contains a node with the given key
|
|
* @param {Key} key
|
|
* @return {boolean} true/false
|
|
*/
|
|
contains (key) {
|
|
var node = this._root;
|
|
var comparator = this._compare;
|
|
while (node) {
|
|
var cmp = comparator(key, node.key);
|
|
if (cmp === 0) return true;
|
|
else if (cmp < 0) node = node.left;
|
|
else node = node.right;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
remove (key) {
|
|
var z = this.find(key);
|
|
|
|
if (!z) return false;
|
|
|
|
this.splay(z);
|
|
|
|
if (!z.left) this.replace(z, z.right);
|
|
else if (!z.right) this.replace(z, z.left);
|
|
else {
|
|
var y = this.minNode(z.right);
|
|
if (y.parent !== z) {
|
|
this.replace(y, y.right);
|
|
y.right = z.right;
|
|
y.right.parent = y;
|
|
}
|
|
this.replace(z, y);
|
|
y.left = z.left;
|
|
y.left.parent = y;
|
|
}
|
|
|
|
this._size--;
|
|
return true;
|
|
}
|
|
|
|
|
|
removeNode(z) {
|
|
if (!z) return false;
|
|
|
|
this.splay(z);
|
|
|
|
if (!z.left) this.replace(z, z.right);
|
|
else if (!z.right) this.replace(z, z.left);
|
|
else {
|
|
var y = this.minNode(z.right);
|
|
if (y.parent !== z) {
|
|
this.replace(y, y.right);
|
|
y.right = z.right;
|
|
y.right.parent = y;
|
|
}
|
|
this.replace(z, y);
|
|
y.left = z.left;
|
|
y.left.parent = y;
|
|
}
|
|
|
|
this._size--;
|
|
return true;
|
|
}
|
|
|
|
|
|
erase (key) {
|
|
var z = this.find(key);
|
|
if (!z) return;
|
|
|
|
this.splay(z);
|
|
|
|
var s = z.left;
|
|
var t = z.right;
|
|
|
|
var sMax = null;
|
|
if (s) {
|
|
s.parent = null;
|
|
sMax = this.maxNode(s);
|
|
this.splay(sMax);
|
|
this._root = sMax;
|
|
}
|
|
if (t) {
|
|
if (s) sMax.right = t;
|
|
else this._root = t;
|
|
t.parent = sMax;
|
|
}
|
|
|
|
this._size--;
|
|
}
|
|
|
|
/**
|
|
* Removes and returns the node with smallest key
|
|
* @return {?Node}
|
|
*/
|
|
pop () {
|
|
var node = this._root, returnValue = null;
|
|
if (node) {
|
|
while (node.left) node = node.left;
|
|
returnValue = { key: node.key, data: node.data };
|
|
this.remove(node.key);
|
|
}
|
|
return returnValue;
|
|
}
|
|
|
|
|
|
/* eslint-disable class-methods-use-this */
|
|
|
|
/**
|
|
* Successor node
|
|
* @param {Node} node
|
|
* @return {?Node}
|
|
*/
|
|
next (node) {
|
|
var successor = node;
|
|
if (successor) {
|
|
if (successor.right) {
|
|
successor = successor.right;
|
|
while (successor && successor.left) successor = successor.left;
|
|
} else {
|
|
successor = node.parent;
|
|
while (successor && successor.right === node) {
|
|
node = successor; successor = successor.parent;
|
|
}
|
|
}
|
|
}
|
|
return successor;
|
|
}
|
|
|
|
|
|
/**
|
|
* Predecessor node
|
|
* @param {Node} node
|
|
* @return {?Node}
|
|
*/
|
|
prev (node) {
|
|
var predecessor = node;
|
|
if (predecessor) {
|
|
if (predecessor.left) {
|
|
predecessor = predecessor.left;
|
|
while (predecessor && predecessor.right) predecessor = predecessor.right;
|
|
} else {
|
|
predecessor = node.parent;
|
|
while (predecessor && predecessor.left === node) {
|
|
node = predecessor;
|
|
predecessor = predecessor.parent;
|
|
}
|
|
}
|
|
}
|
|
return predecessor;
|
|
}
|
|
/* eslint-enable class-methods-use-this */
|
|
|
|
|
|
/**
|
|
* @param {forEachCallback} callback
|
|
* @return {SplayTree}
|
|
*/
|
|
forEach(callback) {
|
|
var current = this._root;
|
|
var s = [], done = false, i = 0;
|
|
|
|
while (!done) {
|
|
// Reach the left most Node of the current Node
|
|
if (current) {
|
|
// Place pointer to a tree node on the stack
|
|
// before traversing the node's left subtree
|
|
s.push(current);
|
|
current = current.left;
|
|
} else {
|
|
// BackTrack from the empty subtree and visit the Node
|
|
// at the top of the stack; however, if the stack is
|
|
// empty you are done
|
|
if (s.length > 0) {
|
|
current = s.pop();
|
|
callback(current, i++);
|
|
|
|
// We have visited the node and its left
|
|
// subtree. Now, it's right subtree's turn
|
|
current = current.right;
|
|
} else done = true;
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
|
|
/**
|
|
* Walk key range from `low` to `high`. Stops if `fn` returns a value.
|
|
* @param {Key} low
|
|
* @param {Key} high
|
|
* @param {Function} fn
|
|
* @param {*?} ctx
|
|
* @return {SplayTree}
|
|
*/
|
|
range(low, high, fn, ctx) {
|
|
const Q = [];
|
|
const compare = this._compare;
|
|
let node = this._root, cmp;
|
|
|
|
while (Q.length !== 0 || node) {
|
|
if (node) {
|
|
Q.push(node);
|
|
node = node.left;
|
|
} else {
|
|
node = Q.pop();
|
|
cmp = compare(node.key, high);
|
|
if (cmp > 0) {
|
|
break;
|
|
} else if (compare(node.key, low) >= 0) {
|
|
if (fn.call(ctx, node)) return this; // stop if smth is returned
|
|
}
|
|
node = node.right;
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Returns all keys in order
|
|
* @return {Array<Key>}
|
|
*/
|
|
keys () {
|
|
var current = this._root;
|
|
var s = [], r = [], done = false;
|
|
|
|
while (!done) {
|
|
if (current) {
|
|
s.push(current);
|
|
current = current.left;
|
|
} else {
|
|
if (s.length > 0) {
|
|
current = s.pop();
|
|
r.push(current.key);
|
|
current = current.right;
|
|
} else done = true;
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns `data` fields of all nodes in order.
|
|
* @return {Array<Value>}
|
|
*/
|
|
values () {
|
|
var current = this._root;
|
|
var s = [], r = [], done = false;
|
|
|
|
while (!done) {
|
|
if (current) {
|
|
s.push(current);
|
|
current = current.left;
|
|
} else {
|
|
if (s.length > 0) {
|
|
current = s.pop();
|
|
r.push(current.data);
|
|
current = current.right;
|
|
} else done = true;
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns node at given index
|
|
* @param {number} index
|
|
* @return {?Node}
|
|
*/
|
|
at (index) {
|
|
// removed after a consideration, more misleading than useful
|
|
// index = index % this.size;
|
|
// if (index < 0) index = this.size - index;
|
|
|
|
var current = this._root;
|
|
var s = [], done = false, i = 0;
|
|
|
|
while (!done) {
|
|
if (current) {
|
|
s.push(current);
|
|
current = current.left;
|
|
} else {
|
|
if (s.length > 0) {
|
|
current = s.pop();
|
|
if (i === index) return current;
|
|
i++;
|
|
current = current.right;
|
|
} else done = true;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Bulk-load items. Both array have to be same size
|
|
* @param {Array<Key>} keys
|
|
* @param {Array<Value>} [values]
|
|
* @param {Boolean} [presort=false] Pre-sort keys and values, using
|
|
* tree's comparator. Sorting is done
|
|
* in-place
|
|
* @return {AVLTree}
|
|
*/
|
|
load(keys = [], values = [], presort = false) {
|
|
if (this._size !== 0) throw new Error('bulk-load: tree is not empty');
|
|
const size = keys.length;
|
|
if (presort) sort(keys, values, 0, size - 1, this._compare);
|
|
this._root = loadRecursive(null, keys, values, 0, size);
|
|
this._size = size;
|
|
return this;
|
|
}
|
|
|
|
|
|
min() {
|
|
var node = this.minNode(this._root);
|
|
if (node) return node.key;
|
|
else return null;
|
|
}
|
|
|
|
|
|
max() {
|
|
var node = this.maxNode(this._root);
|
|
if (node) return node.key;
|
|
else return null;
|
|
}
|
|
|
|
isEmpty() { return this._root === null; }
|
|
get size() { return this._size; }
|
|
|
|
|
|
/**
|
|
* Create a tree and load it with items
|
|
* @param {Array<Key>} keys
|
|
* @param {Array<Value>?} [values]
|
|
|
|
* @param {Function?} [comparator]
|
|
* @param {Boolean?} [presort=false] Pre-sort keys and values, using
|
|
* tree's comparator. Sorting is done
|
|
* in-place
|
|
* @param {Boolean?} [noDuplicates=false] Allow duplicates
|
|
* @return {SplayTree}
|
|
*/
|
|
static createTree(keys, values, comparator, presort, noDuplicates) {
|
|
return new SplayTree(comparator, noDuplicates).load(keys, values, presort);
|
|
}
|
|
}
|
|
|
|
|
|
function loadRecursive (parent, keys, values, start, end) {
|
|
const size = end - start;
|
|
if (size > 0) {
|
|
const middle = start + Math.floor(size / 2);
|
|
const key = keys[middle];
|
|
const data = values[middle];
|
|
const node = { key, data, parent };
|
|
node.left = loadRecursive(node, keys, values, start, middle);
|
|
node.right = loadRecursive(node, keys, values, middle + 1, end);
|
|
return node;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
|
|
function sort(keys, values, left, right, compare) {
|
|
if (left >= right) return;
|
|
|
|
const pivot = keys[(left + right) >> 1];
|
|
let i = left - 1;
|
|
let j = right + 1;
|
|
|
|
while (true) {
|
|
do i++; while (compare(keys[i], pivot) < 0);
|
|
do j--; while (compare(keys[j], pivot) > 0);
|
|
if (i >= j) break;
|
|
|
|
let tmp = keys[i];
|
|
keys[i] = keys[j];
|
|
keys[j] = tmp;
|
|
|
|
tmp = values[i];
|
|
values[i] = values[j];
|
|
values[j] = tmp;
|
|
}
|
|
|
|
sort(keys, values, left, j, compare);
|
|
sort(keys, values, j + 1, right, compare);
|
|
}
|