How can I unite two trees in javascript?

Viewed 197

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);
1 Answers

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();

Related