diff --git a/avl.js b/avl.js new file mode 100644 index 0000000..37315f7 --- /dev/null +++ b/avl.js @@ -0,0 +1,162 @@ +Array.prototype.equals = function(other) { + if (this.length != other.length) { + return false; + } + + let i = this.length; + while (--i >= 0) { + if (this[i] !== other[i]) { + return false; + } + } + + return true; +} + +function balanceFactor(node) { + if (!node) { + return 0; + } + + let [left, right] = [nodeRank(node.left), nodeRank(node.right)]; + return right - left; +} + +function updateRank(node) { + let [left, right] = [nodeRank(node.left), nodeRank(node.right)]; + node.rank = 1 + Math.max(left, right); +} + +class AVLTree extends RankedTree { + isCorrectNode(node, recursive) { + if (!node) { + return true; + } + + recursive = recursive ?? true; + + // FIXME: not enough, must be checked against differences + // let bf = balanceFactor(node); + // if (bf < -1 || bf > 1) { + // return false; + // } + + let differences = nodeDifferences(node); + differences.sort(); + if (!differences.equals([1, 1]) && !differences.equals([1, 2])) { + return false; + } + + return !recursive || (this.isCorrectNode(node.left) && this.isCorrectNode(node.right)); + } + + fix0Child(x, y, z, rotateLeft, rotateRight) { + let newRoot = x.parent; + + if (!y || nodeDifference(y) == 2) { + newRoot = rotateRight(z); + z.demote(); + } else if (nodeDifference(y) == 1) { + rotateLeft(x); + newRoot = rotateRight(z); + + y.promote(); + x.demote(); + z.demote(); + } + + return newRoot; + } + + insertRebalance(x) { + let diffs = nodeDifferences(x.parent).sort(); + while (x.parent && diffs.equals([0, 1])) { + x.parent.promote(); + x = x.parent; + + diffs = nodeDifferences(x.parent).sort(); + } + + if (!x.parent) { + return; + } + + let rotatingAroundRoot = x.parent.parent == null; + let newRoot = x.parent; + + let rankDifference = nodeDifference(x); + if (rankDifference != 0) { + return; + } + + let xParent = x.parent; + + if (rankDifference == 0 && x.parent.left === x) { + newRoot = this.fix0Child( + x, x.right, xParent, rotateLeft, rotateRight + ); + } else if (rankDifference == 0 && x.parent.right === x) { + newRoot = this.fix0Child( + x, x.left, xParent, rotateRight, rotateLeft + ); + } + + if (rotatingAroundRoot) { + this.root = newRoot; + } + } + + deleteRotate(x, y, leaning, rotatingAroundRoot, rotateLeft, rotateRight) { + let newRoot = x; + + let factor = balanceFactor(y); + switch (factor) { + case 0: + case leaning: + newRoot = rotateLeft(x); + break; + default: + rotateRight(y); + newRoot = rotateLeft(x); + break; + } + + [newRoot.left, newRoot.right, newRoot].filter(x => x).forEach(n => { + updateRank(n) + }); + + if (rotatingAroundRoot) { + this.root = newRoot; + } + return factor == 0; + } + + deleteFixup(y, parent) { + let x = (y) ? y : parent; + + let factor = balanceFactor(x); + switch (factor) { + case 0: + updateRank(x); + return false; + case -1: + case 1: + return true; + } + + let rotatingAroundRoot = x.parent == null; + let [z, leaning, toLeft, toRight] = [x.left, -1, rotateRight, rotateLeft]; + if (factor == 2) { + [z, leaning, toLeft, toRight] = [x.right, 1, rotateLeft, rotateRight]; + } + + return this.deleteRotate(x, z, leaning, rotatingAroundRoot, toLeft, toRight); + } + + deleteRebalance(node, parent) { + while (node || parent) { + this.deleteFixup(node, parent); + [node, parent] = [parent, (parent) ? parent.parent : null]; + } + } +} \ No newline at end of file diff --git a/node.js b/node.js new file mode 100644 index 0000000..66135f2 --- /dev/null +++ b/node.js @@ -0,0 +1,172 @@ +const LEAF = 0; +const UNARY = 1; +const BINARY = 2; + +function nodeHeight(node) { + if (node == null) { + return -1; + } + return 1 + Math.max(nodeHeight(node.left), nodeHeight(node.right)); +} + +function nodeRank(node) { + if (node == null) { + return -1; + } + return node.rank; +} + +function nodeDifference(node, parent) { + if (parent === undefined) { + parent = node != null ? node.parent : null; + } + return nodeRank(parent) - nodeRank(node); +} + +function nodeDifferences(node) { + let r = nodeRank(node); + let left = null; + let right = null; + + if (node != null) { + left = node.left; + right = node.right; + } + + return [r - nodeRank(left), r - nodeRank(right)]; +} + +function rotateRight(x) { + let parent = x.parent; + let y = x.left; + // let z = x.right; + + if (parent != null) { + if (parent.left === x) { + parent.left = y; + } else { + parent.right = y; + } + } + + x.left = y.right; + if (x.left != null) { + x.left.parent = x; + } + + y.right = x; + x.parent = y; + y.parent = parent; + + return y; +} + +function rotateLeft(x) { + let parent = x.parent; + // let y = x.left; + let z = x.right; + + if (parent != null) { + if (parent.left === x) { + parent.left = z; + } else { + parent.right = z; + } + } + + x.right = z.left; + if (x.right != null) { + x.right.parent = x; + } + + z.left = x; + x.parent = z; + z.parent = parent; + + return z; +} + +function findParentNode(value, node, missing) { + if (missing === undefined) { + missing = true; + } + + newNode = node + + while (newNode && (missing || newNode.value != value)) { + node = newNode; + if (value < node.value) { + newNode = node.left; + } else if (node.value < value) { + newNode = node.right; + } else { + // same value + return null; + } + } + + return node; +} + +function nodeSearch(value, node) { + while (node && node.value != value) { + node = (value < node.value) ? node.left : node.right; + } + + return node; +} + +function nodeMinimum(node) { + while (node.left != null) { + node = node.left; + } + return node; +} + +function nodeMaximum(node) { + while (node.right != null) { + node = node.right; + } + return node; +} + +class Node { + constructor(value, left, right, parent) { + this.value = value; + this.rank = 0; + + this.parent = parent || null; + this.left = left || null; + this.right = right || null; + } + + type() { + if (this.left != null && this.right != null) { + return BINARY; + } + + if (this.left != null || this.right != null) { + return UNARY; + } + + return LEAF; + } + + serialize() { + return `Node(value=${this.value}, rank = ${this.rank}, left=${this.left}, right=${this.right}, parent=${this.parent})`; + } + + toString() { + return `Node(value=${this.value}, rank=${this.rank})`; + } + + promote() { + this.rank++; + return this; + } + + demote() { + this.rank--; + return this; + } +} \ No newline at end of file diff --git a/ranked_tree.js b/ranked_tree.js new file mode 100644 index 0000000..8bbcf8d --- /dev/null +++ b/ranked_tree.js @@ -0,0 +1,165 @@ +class Queue { + constructor() { + this.q = []; + } + + enqueue(x) { + this.q.push(x); + } + + dequeue() { + return this.q.shift(); + } + + isEmpty() { + return this.q.length == 0; + } +} + +class RankedTree { + constructor() { + this.root = null; + } + + _getUnwrappedGraph() { + let result = ""; + + let q = new Queue(); + q.enqueue(this.root); + + let edges = []; + while (!q.isEmpty()) { + let node = q.dequeue(); + if (!q) { + continue; + } + + result += `"${node.toString()}" [label="${node.value}, ${node.rank}"];\n`; + + [node.left, node.right].filter(child => child).forEach(child => { + edges.push([node, child]); + q.enqueue(child); + }); + } + + edges.forEach(vertices => { + let [u, v] = vertices; + result += `"${u.toString()}" -> "${v.toString()}" [label="${nodeDifference(v)}"]\n`; + }); + + return result; + } + + toString() { + return `digraph {\n${this._getUnwrappedGraph()}}\n`; + } + + rank() { + return nodeRank(this.root); + } + + isCorrect() { + return this.isCorrectNode(this.root); + } + + search(value, node) { + return nodeSearch(value, node || this.root); + } + + insert(value) { + let insertedNode = new Node(value); + + if (!this.root) { + this.root = insertedNode; + return; + } + + let parent = findParentNode(value, this.root); + if (!parent) { + return; + } + + insertedNode.parent = parent; + + if (value < parent.value) { + parent.left = insertedNode; + } else { + parent.right = insertedNode; + } + + this.insertRebalance(insertedNode); + } + + transplant(u, v) { + if (!u.parent) { + this.root = v; + } else if (u.parent.left === u) { + u.parent.left = v; + } else { + u.parent.right = v; + } + + if (v) { + v.rank = u.rank; + v.parent = u.parent; + } + } + + deleteNode(node) { + if (!node) { + return null; + } + + let [y, parent] = [null, node.parent]; + + if (!node.left) { + y = node.right; + this.transplant(node, node.right); + } else if (!node.right) { + y = node.left; + this.transplant(node, node.left); + } else { + let successor = nodeMinimum(node.right); + parent = (successor.parent != node) ? successor.parent : successor; + + if (successor.parent != node) { + parent = (successor.right) ? successor.right : successor.parent; + this.transplant(successor, successor.right); + successor.right = node.right; + successor.right.parent = successor; + } + + this.transplant(node, successor); + successor.left = node.left; + node.left.parent = successor; + } + + return [y, parent]; + } + + delete(value) { + let node = this.root; + while (node && node.value != value) { + node = (value < node.value) ? node.left : node.right; + } + + let toRebalance = this.deleteNode(node); + if (toRebalance) { + let [y, parent] = toRebalance; + this.deleteRebalance(y, parent); + } + } + + /* abstract methods */ + isCorrectNode(node) { + throw "not implemented!"; + } + + insertRebalance(node) { + throw "not implemented!"; + } + + deleteRebalance(node, parent) { + throw "not implemented!"; + } +} \ No newline at end of file diff --git a/ravl.js b/ravl.js new file mode 100644 index 0000000..97bfcde --- /dev/null +++ b/ravl.js @@ -0,0 +1,18 @@ +class RAVLTree extends WAVLTree { + isCorrectNode(node, recursive) { + if (!node) { + return true; + } + + if (!nodeDifferences(node).filter(d => d <= 0).length > 0) { + return false; + } + + return !recursive || (this.isCorrectNode(node.left) && this.isCorrectNode(node.right)); + } + + deleteRebalance(node, parent) { + // no-op + return; + } +} \ No newline at end of file diff --git a/wavl.js b/wavl.js new file mode 100644 index 0000000..71753bc --- /dev/null +++ b/wavl.js @@ -0,0 +1,123 @@ +class WAVLTree extends AVLTree { + isCorrectNode(node, recursive) { + if (!node) { + return true; + } + + nodeDifferences(node).forEach(childRank => { + if ([1, 2].findIndex(childRank) == -1) { + return false; + } + }); + + if (node.type() == LEAF) { + return node.rank == 0; + } + + return !(recursive ?? true) || (this.isCorrectNode(node.left) && this.isCorrectNode(node.right)); + } + + fixDelete(x, y, z, reversed, rotateLeft, rotateRight) { + let newRoot = x; + let [v, w] = [y.left, y.right]; + + if (reversed) { + [v, w] = [w, v]; + } + + let wDiff = nodeDifference(w, y); + + if (wDiff == 1 && y.parent) { + newRoot = rotateLeft(y.parent); + + y.promote(); + z.demote(); + + if (z.type() == LEAF) { + z.demote(); + } + } else if (wDiff == 2 && v && v.parent) { + rotateRight(v.parent); + newRoot = rotateLeft(v.parent); + + v.promote().promote(); + y.demote(); + z.demote().demote(); + } + + return newRoot; + } + + bottomUpDelete(x, parent) { + let xDiff = nodeDifference(x, parent); + if (xDiff != 3 || !parent) { + return; + } + + let y = (parent.left === x) ? parent.right : parent.left; + let yDiff = nodeDifference(y, parent); + + while (parent && xDiff == 3 && y && (yDiff == 2 || nodeDifferences(y).equals([2, 2]))) { + parent.demote(); + if (yDiff != 2) { + y.demote(); + } + + x = parent; + parent = x.parent; + if (!parent) { + return; + } + + let y = (parent.left === x) ? parent.right : parent.left; + + xDiff = nodeDifference(x, parent); + yDiff = nodeDifference(y, parent); + } + + if (!parent) { + return; + } + + let rotatingAroundRoot = parent.parent == null; + let newRoot = parent; + + let parentNodeDiffs = nodeDifferences(parent); + if (parentNodeDiffs.sort().equals([1, 3])) { + if (parent.left === x) { + newRoot = this.fixDelete(x, parent.right, parent, false, rotateLeft, rotateRight); + } else { + newRoot = this.fixDelete(x, parent.left, parent, true, rotateRight, rotateLeft); + } + } + + if (rotatingAroundRoot) { + this.root = newRoot; + } + } + + deleteFixup(y, parent) { + let z = (y) ? y : parent; + + if (nodeDifferences(z).equals([2, 2])) { + z.demote(); + } + + if (!parent) { + return; + } + + [parent.left, parent.right].forEach(y => { + if (nodeDifference(y, parent) == 3) { + this.bottomUpDelete(y, parent); + } + }); + } + + deleteRebalance(node, parent) { + while (node || parent) { + this.deleteFixup(node, parent); + [node, parent] = parent, (parent) ? parent.parent : null; + } + } +} \ No newline at end of file