Splay Tree implementation: Search function - Zag zag rotation wrong - javascript

I am testing Zag-zag rotation with a right-skewed tree. Before that, I had tested Zig-zig, Zig-zag rotation with a left-skewed tree and OK.
Here is my code:
// An AVL tree node
class Node {
constructor(key) {
this.key = key;
this.left = this.right = null;
}
}
function rightRotate(root) {
const rootLeft = root.left; // saving the reference
root.left = rootLeft.right;
rootLeft.right = root;
return rootLeft; // new root
}
function leftRotate(root) {
const rootRight = root.right;
root.right = rootRight.left;
rootRight.left = root;
return rootRight;
}
// This function modifies the tree and returns the new root
// - If has key, Brings the key at root
// - Else, brings the last accessed item at root.
function splay(root, key) {
// Base cases
if (root === null || root.key === key) return root; // bring last accessed element or founded element as root of left-left (sub)tree
// Grandparent start here
// Key lies in Left
if (key < root.key) {
// Key is not in tree
if (root.left === null) return root; // If not founded, Bring last accessed element root of left (sub)tree
// Parent start here
// Zig-Zig (Left Left)
if (key < root.left.key) {
root.left.left = splay(root.left.left, key);
// Do first rotation for root, brind child as parent of subtree
root = rightRotate(root);
}
// Zig-Zag (Left Right)
else if (key > root.left.key) {
root.left.right = splay(root.left.right, key);
// Do first rotation for root.left, brind child as parent of subtree
if (root.left.right != null) root.left = leftRotate(root.left);
}
// 1 cong 2 viec
// 1. Do second rotation, bring child as grandparent of subtree
// 2. Bring parent (level 2) as root of tree when last recursive splay() finish
return rightRotate(root);
// Parent end
}
// Key lies in Right
else {
if (root.right === null) return root;
// Parent start here
// Zag-Zag (Right Right)
if (key > root.right.key) {
root.right.right = splay(root.right.right, key);
root = leftRotate(root);
}
// Zag-Zig (Right Left)
else if (key < root.right.key) {
root.right.left = splay(root.right.left, key);
if (root.right.left != null) root.right = rightRotate(root.right);
}
return leftRotate(root);
// End parent
}
// Grandparent end
}
// The search function for Splay tree.
// This function returns the new root of Splay Tree.
// If key is present in tree then, it is moved to root.
// else, last accessed element is moved to root
function search(root, key) {
return splay(root, key);
}
// A utility function to print
// preorder traversal of the tree.
// The function also prints height of every node
function preOrder(root) {
if (root != null) {
console.log(root.key);
preOrder(root.left);
preOrder(root.right);
}
}
// Right-skewed tree for Zag-zag and Zag-zig test
// Using root2 to console.log in other function like splay, search (because they have argument name "root")
let root2 = new Node(10);
root2.right = new Node(15);
root2.right.right = new Node(16);
root2.right.right.right = new Node(20);
root2.right.right.right.right = new Node(21);
root2.right.right.right.right.right = new Node(22);
root2 = splay(root2, 20);
console.log(root2)
Assume we search for node 20. The rotation should happen as follow:
Zag-zag rotation: Replace node 15 with 20.
Zag rotation: Replace node 10 with 20
However, my code result is:
Demo of image used this

Both your algorithm and the imaged algorithm perform double rotations correctly, every time taking a pair of edges on the path to the target node, and applying a double rotation on that pair.
However, when the path to the target node has an odd length, then there is one edge that will undergo a single rotation. The choice of this single edge is different:
your algorithm will "split" the path in pairs starting from the root node, treating the remaining single edge at the end of the path separately.
the imaged algorithm will "split" the path in pairs starting from the target node, treating the remaining single edge at the start of the path (at the root) separately.
Here is your code aligned with that second strategy:
function splay(root, key) {
function splaySub(root) {
if (!root) throw new RangeError; // Value not found in tree
if (root.key === key) return root;
let side = key < root.key ? "left" : "right";
root[side] = splaySub(root[side]);
// Check if odd: then caller to deal with rotation
if (root[side].key === key) return root;
// Apply a double rotation, top-down:
root = key < root.key ? rightRotate(root) : leftRotate(root);
return key < root.key ? rightRotate(root) : leftRotate(root);
}
try {
root = splaySub(root);
return !root || root.key === key
// Path had even length: all OK
? root
// Odd length: Perform a final, single rotation
: key < root.key ? rightRotate(root) : leftRotate(root);
} catch(e) {
if (!(e instanceof RangeError)) throw e;
return root; // Not found: return original tree
}
}
When the searched value is not in the tree, this code will trigger an Exception, so to exit quickly out of recursion without any update to the tree. In this case that approach makes the code a bit cleaner (fewer checks for null values...).
Addendum
As in comments you explained that the splay function should also bring a node to the top when it doesn't find the value, I provide here the updated code. I took the opportunity to turn the code into more OOP style, so you'll find here some of your functions turned into methods:
class Node {
constructor(key, left=null, right=null) {
this.key = key;
this.left = left;
this.right = right;
}
* inOrder(depth=0) {
if (this.right) yield * this.right.inOrder(depth+1);
yield [depth, this.key];
if (this.left) yield * this.left.inOrder(depth+1);
}
_rotate(toRight) {
const [side, otherSide] = toRight ? ["right", "left"] : ["left", "right"];
const orig = this[otherSide];
this[otherSide] = orig[side];
orig[side] = this;
return orig;
}
rightRotate() {
return this._rotate(true);
}
leftRotate() {
return this._rotate(false);
}
insert(key) {
const side = key < this.key ? "left" : "right";
if (this[side]) return this[side].insert(key);
this[side] = new Node(key);
}
}
class SplayTree {
constructor(...values) {
this.root = null;
for (const value of values) this.insert(value);
}
insert(value) {
if (this.root) this.root.insert(value);
else this.root = new Node(value);
}
splay(key) {
if (!this.root) return;
function splaySub(root) {
if (root.key === key) return root;
const side = key < root.key ? "left" : "right";
// Not found? Then do as if we looked for current node's key
if (!root[side]) {
key = root.key; // Modifies the outer-scoped variable
return root;
}
root[side] = splaySub(root[side]);
// Check if odd: then caller to deal with rotation
if (root[side].key === key) return root;
// Apply a double rotation, top-down:
root = root._rotate(key < root.key);
return root._rotate(key < root.key);
}
this.root = splaySub(this.root);
if (this.root.key !== key) this.root = this.root._rotate(key < this.root.key);
}
search(key) {
this.splay(key);
}
* inOrder() {
if (this.root) yield * this.root.inOrder();
}
toString() {
return Array.from(tree.inOrder(), ([depth, key]) => " ".repeat(depth) + key).join("\n");
}
}
const tree = new SplayTree(10, 15, 16, 20, 21, 22);
console.log("Initial tree:");
console.log(tree.toString());
tree.search(19); // Not found, so it splays 20.
console.log("Final tree:");
console.log(tree.toString());

Related

How can I unite two trees in javascript?

I'm implementing an algorithm (Kruslkal) that needs to merge two or more binary trees in javascript, for example:
The following tree:
4
5 6
Can be merged into the following tree:
2
1 3
... resulting:
2
1 3
4
5 6
I put the binary tree data structure code, but when I did a test in a function that merges the trees, called 'merge', nothing happens. The first tree is not merged in the second tree, and if I try to use console.log in the function 'merge', the following message appears: "Uncaught TypeError: tree is null".
Can someone help me with this?
function binarytree()
{
this.root = null;
this.add = function(value)
{
var node = {
value : value,
left : null,
right : null
};
var current;
if (this.root == null) this.root = node;
else
{
current = this.root;
while (1)
{
if (value < current.value)
{
if (current.left == null)
{
current.left = node;
break;
}
else current = current.left;
}
else if (value > current.value)
{
if (current.right == null)
{
current.right = node;
break;
}
else current = current.right;
}
else break;
}
}
}
this.search = function(value)
{
var found = false,
current = this.root;
while (!found && current)
{
if (value < current.value) current = current.left;
else if (value > current.value) current = current.right;
else found = true;
}
return found;
}
this.print = function(no)
{
if (no)
{
this.print(no.left);
this.print(no.right);
console.log(no.value);
}
}
}
var tree = new binarytree();
var tree2 = new binarytree();
function merge(tree, tree2)
{
//console.log("tree.value " + tree.value);
if (tree == null) tree = tree2.root;
else if (tree.value < tree2.value) this.merge(tree.left, tree2);
else this.merge(tree.right, tree2);
}
tree.add(1);
tree.add(2);
tree.add(3);
console.log("First tree:");
tree.print(tree.root);
tree2.add(7);
tree2.add(8);
tree2.add(9);
console.log("Second tree:");
tree2.print(tree2.root);
merge(tree.root,tree2.root);
console.log("Merged trees:");
tree.print(tree.root);
Looking at your code, it is clear that you are dealing with not just any binary trees, but binary search trees. These trees ensure that the value of a node is never less than the value of its left child, and never greater than the value of its right child.
Your example is therefore not correctly pictured. This is not a binary search tree:
4
5 6
It would be correct if it were:
5
4 6
Moreover, your code is not creating these trees. Instead it is creating these trees:
1 and 7
2 8
3 9
If you want to create more balanced trees, you should change the order of the insertions. For example:
tree.add(2); // First!
tree.add(1);
tree.add(3);
This will create:
2
1 3
The Error
...if I try to use console.log in the function 'merge', the following message appears: "Uncaught TypeError: tree is null".
This is expected, as you make recursive calls like with this.merge(tree.left, tree2), and tree.left can be null. Even in the next statement you check this case with if (tree == null), so it is normal you get this error.
But your code shows that you think that an assignment to tree with tree = tree2.root; will somehow perform the attachment of tree2 inside tree. But this is just an assignment to a variable, not to a left or right property of a node in the tree, so nothing is happening to the tree with this assignment. Remember that JavaScript passes values, so when you pass tree.left as argument to a function, you can be sure that tree.left will still reference the same object once the function has returned.
In short, you should make the assignment one step earlier, when you arrive at a leaf, not when you arrive at a null. Something like this:
function merge(tree, tree2) {
if (tree2.value < tree.value) {
if (tree.left) {
this.merge(tree.left, tree2);
} else {
tree.left = tree2;
}
} else {
if (tree.right) {
this.merge(tree.right, tree2);
} else {
tree.right = tree2;
}
}
}
The deeper problem
However, while the above will perform a simple attachment of one tree to another, it assumes that the range of values of the first tree does not overlap with the range of values in the second tree. If there is an overlap, this procedure will not produce a binary search tree. A merge that maintains the BST property, will need to distribute the nodes of the second tree at different places in the first tree.
One way to do that is to take every value of the second tree and call add(value) on the first tree. This will work fine. It has a time complexity of O(nlogm), where m is the size of the first tree, and n of the second tree.
If the tree sizes are comparable, you'll get a better time complexity when you walk through the first tree in one sweep, inserting new nodes as you pass by the right insertion spot. This will have a time complexity of O(m+n).
Implementation
I would change a lot to your code:
Use class syntax... and define methods on the prototype, not on each instance
Define an iterator to visit nodes in inorder sequence
Avoid the code repetition in add and search.
Define a class for constructing node objects instead of using an object literal for that
... several other improvements
Here it is:
class Node { // Create a class for this
constructor(value, left=null, right=null) {
this.value = value;
this.left = left;
this.right = right;
}
* inorder() {
if (this.left) yield * this.left.inorder();
yield this.value;
if (this.right) yield * this.right.inorder();
}
}
class BinaryTree { // Use class syntax and PascalCase
constructor() {
this.root = null;
}
add(value) {
let [location, side] = this.locate(value);
if (side) location[side] = new Node(value); // Use constructor instead of plain object literal;
}
locate(value) { // Returns where node is or should be inserted
if (!this.root) return [this, "root"];
let current = this.root;
while (true) {
if (value < current.value) {
if (!current.left) return [current, "left"];
current = current.left;
} else if (value > current.value) {
if (!current.right) return [current, "right"];
current = current.right;
}
else return [current, ""];
}
}
search(value) {
return !this.locate(value)[1];
}
print() { // Use iterator to get the values
for (let value of this.inorder()) console.log(value);
}
* inorder(node) {
if (this.root) yield * this.root.inorder();
}
merge(otherTree) {
let values = otherTree.inorder();
let nextValue = values.next().value;
function recur(node, max) {
while (nextValue !== undefined && nextValue < max) {
if (nextValue < node.value) {
if (!node.left) {
node.left = new Node(nextValue);
nextValue = values.next().value;
}
recur(node.left, node.value);
} else if (nextValue > node.value) {
if (!node.right) {
node.right = new Node(nextValue);
nextValue = values.next().value;
}
recur(node.right, max);
} else {
nextValue = values.next().value;
}
}
}
recur(this.root, Infinity);
}
}
var tree = new BinaryTree();
var tree2 = new BinaryTree();
tree.add(2);
tree.add(4);
tree.add(6);
console.log("First tree:");
tree.print();
tree2.add(1);
tree2.add(3);
tree2.add(5);
console.log("Second tree:");
tree2.print();
tree.merge(tree2);
console.log("Merged trees:");
tree.print();

Implementing BST TREE DUPLICATES KEYS - JS

I'm trying to implement a self balancing tree (beginning with BST first ).
So i have multiple keys with same value. So i have decided to make primary and secondary comparison to place a value in a node.
My node structure looks like ->
var Node = function (value) {
this.fine = value.fine ; // primary comparison
this.index = value.index ; // secondary comparison
this.left = null ;
this.right = null ;
};
fine is int value and index is array index.
my BST insert looks like this->
function insert(root, data) {
if(root == null) {
root = new Node(data)
}
// base case for secondary comparison .
if(root.fine === data.fine ) {
if(root.index > data.index ) {
root.left = new Node(data) ;
} else {
root.right = new Node(data)
}
}
if(root.fine > data.fine ) {
root.left = insert(root.left, data) ;
} else if(root.fine < data.fine ) {
root.right = insert(root.right , data) ;
return root;
}
but i am getting MAXIMUM STACK CALL. i think the base case is not right! What should be the base case of this problem?

Remove nodes from Linked List based on a value

I am working through a Hackerrank problem and I am trying to remove all nodes that are greater than a particular value.
This is their basic implementation
const SinglyLinkedListNode = class{
constructor(value) {
this.value = value;
this.next = null;
}
};
const SinglyLinkedList = class {
constructor() {
this.head = null;
this.tail = null;
}
insertNode(value) {
const node = new SinglyLinkedListNode(value);
if(!this.head) {
this.head = node;
} else {
this.tail.next = node;
}
this.tail = node;
}
};
My function to removeNodes is as follows...
SinglyLinkedList.prototype.removeNodes = function(listHead, x){
let currentNode = listHead;
while(currentNode !== null && currentNode.next !== null) {
let next = currentNode.next;
while (next !== null && next.value > x) {
next = next.next
}
currentNode.next = next
if(currentNode.next === null) {
break;
}
}
return currentNode
}
The parameters are: listhead - reference to the root node, and x - the integer value to filter the Linked List
So for example, the LL is 1-> 2 -> 4 -> 3 -> 5 and I need to remove all nodes greater than x (3) and maintain the integrity of the LL order.
The result should be 1 -> 2 -> 3.
I am getting confused on why I keep getting this.tail.value = 5 instead of
this.tail.value = 3, this.tail.next = null.
this is a REPL
Cause the tail has to be rewritten explicitly as it keeps a reference to an unlinked node otherwise. Before running the function the list looks like:
list: head tail
1 -> 2 -> 3 -> 4 -> 5
and afterwards the tail still points to 5 although that was unlinked:
list: head tail
1 -> 2 -> 3 5
To resolve this, just rewrite the tail at the end of your function:
return this.tail = currentNode;
Also you have to actually traverse the list, so add a
currentNode = currentNode.next;
at the end of the outer while.

How to store depth of Binary Search Tree

I'm trying to determine if a Binary Search Tree is balanced. I'm not too clear on how to store the depth of the childnodes of the left and right branch. I'm trying to return true if the right branch is greater than the left branch by a length of a max of 1 and vice versa.
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* #param {TreeNode} root
* #return {boolean}
*/
var isBalanced = function(root) {
var checkChild = function(root) {
if (this.left) {
var left = 1;
this.left.checkChild(root);
if (this.right) {
left += 1;
this.right.checkChild(root);
}
if (this.right) {
var right = 1;
this.right.checkChild(root);
if (this.left) {
right += 1;
this.right.checkChild(root);
}
}
}
if (left - right > 1 || right - left > 1) {
return false;
}
return true;
};
};
I was thinking of creating a var to increment every-time a node is traversed for both the right and left branches starting from the head. But I'm realizing that this will compare the total number of nodes from the left branch to the total number of nodes on the right branch, which won't work.
At each check why are you sending the head again like
why root again?
this.left.checkChild(root)
Instead, if you want to find the depth, your implementation should look something like this:
function treeDepth(tree)
{
if (tree === null)
return 0;
else
{
/* compute the depth of each subtree */
let leftDepth = treeDepth(tree.left);
let rightDepth = treeDepth(tree.right);
/* use the larger one */
if (leftDepth > rightDepth)
return(leftDepth+1);
else return(rightDepth+1);
}
}
At first find max depth of the root, then find min depth of the root. It is easy using dfs. Next step is to check difference of these depths.
The code will look like this:
class Node {
constructor(value) {
this.value = value
this.left = null
this.right = null
}
}
var isBalanced = function(root) {
var foo = function(root, fun) {
if (root === null) return 0
l = foo(root.left, fun)
r = foo(root.right, fun)
return fun(l, r);
}
return foo(root, Math.max) - foo(root, Math.min) < 2
}
let tree = new Node(1)
tree.left = new Node(2)
tree.left.left = new Node(3)
tree.left.left.left = new Node(4)
tree.right = new Node(5)
tree.right.left = new Node(6)
document.write(isBalanced(tree))
If you want to use checkChild as a method, you should define it as such, not as a variable. I would also suggest to not return a boolean, but the real difference in depth between left and right subtree. This will give more information to the caller, who can still treat that value as a boolean if so desired (falsy means balanced, truthy means tilted).
Here is how your implementation could look:
class TreeNode {
constructor(val) {
this.val = val;
}
add(node) {
const dir = node.val < this.val ? "left" : "right";
if (this[dir]) {
this[dir].add(node);
} else {
this[dir] = node;
}
}
height() {
return Math.max(
this.left ? this.left.height() + 1 : 0,
this.right ? this.right.height() + 1 : 0
);
}
tilt() {
return (this.left ? this.left.height() + 1 : 0)
- (this.right ? this.right.height() + 1 : 0);
}
static from(...data) {
if (!data.length) return;
const root = new TreeNode(data[0]);
for (let v of data.slice(1)) {
root.add(new TreeNode(v));
}
return root;
}
}
const root = TreeNode.from(13, 4, 9, 16);
console.log(root);
console.log('Tilt at root = ', root.tilt());
.as-console-wrapper { max-height: 100% !important; top: 0; }

BST in javascript using reference objects

I found this algorithm from Data structures and algorithms in javascript. For the insertion method, there are two references to the root (current and parent). My question is, why can't i change both current and parent to this.root? They both point to this.root. The code does not work properly when I do that however
'use strict';
var BST = function() {
this.root = null;
//embed _Node inside BST so it comes with when BST is created
this._Node = function(data, left, right) {
this.data = data;
this.count = 1;
this.left = left;
this.right = right;
};
this._Node.prototype.show = function() {
return this.data;
};
this._Node.prototype.showCount = function() {
return this.count;
}
};
BST.prototype.insert = function(data) {
//use this data for a new node
var n = new this._Node(data, null, null);
//if root is null, put this new node and its data into root
if (this.root === null) {
this.root = n;
}
else {
//find a child position for this node
var current = this.root;
var parent;
while (true) {
parent = current;
if (data < current.data) {
current = current.left;
if (current === null) {
parent.left = n;
break;
}
}
else {
current = current.right;
if (current === null) {
parent.right = n;
break;
}
}
}
}
};
var nums = new BST();
nums.insert(23);
nums.insert(45);
nums.insert(16);
nums.insert(37);
nums.insert(3);
nums.insert(99);
nums.insert(22);
current does not refer to this.root throughout the entire algorithm.
It is initialized as this.root, but then it is quickly reassigned to either current = current.left; or current = current.right;. From that moment on current is no longer this.root. It is either this.root.left or this.root.right.
On the next iteration of the while loop it will get reassigned again, but it will never be this.root again, since it is alway being reassigned to a child node of current.
parent is similar, being this.root only on the first iteration. On each subsequent iteration it is reassigned by parent = current; and since current is no longerthis.root,parentwon't bethis.root` either.
parent is used just to hold reference of the previous node, you create new node n and then find it's position in the tree, once current becomes null, you have found the target position for node n, you need to assign it as child (left or right) to the parent node

Categories