devoured elysium
devoured elysium

Reputation: 105067

Sentinel node in Binary Search Trees

I was wondering if in a way to avoid having to deal with the root as a special case in a Binary Search Tree I could use some sort of sentinel root node?

public void insert(int value) {
    if (root == null) {
        root = new Node(value);
        ++size;
    } else {
        Node node = root;
        while (true) {
            if (value < node.value) {
                if (node.left == null) {
                    node.left = new Node(value);
                    ++size;
                    return;
                } else {
                    node = node.left;
                }
            } else if (value > node.value) {
                if (node.right == null) {
                    node.right = new Node(value);
                    ++size;
                    return;
                } else {
                    node = node.right;
                }
            } else return;
        }
    }
}

For instance, in the insert() operation I have to treat the root node in a special way. In the delete() operation the same will happen, in fact, it will be way worse.

I've thought a bit regarding the issue but I couldn't come with any good solution. Is it because it is simply not possible or am I missing something?

Upvotes: 1

Views: 3057

Answers (2)

Gentian Kasa
Gentian Kasa

Reputation: 776

The root will always be a special case. The root is the entry point to the binary search tree.

Inserting a sentinel root node means that you will have a root node that is built at the same time as the tree. Furthermore, the sentinel as you mean it will just decrease the balance of the tree (the BST will always be at the right/left of its root node).

The only way that pops in my mind to not manage the root node as a special case during insert/delete is to add empty leaf nodes. In this way you never have an empty tree, but instead a tree with an empty node.

During insert() you just replace the empty leaf node with a non-empty node and two new empty leafs (left and right).

During delete(), as a last step (if such operation is implemented as in here) you just empty the node (it becomes an empty leaf) and trim its existing leafs.

Keep in mind that if you implement it this way you will have more space occupied by empty leaf nodes than by nodes with meaningful data. So, this implementation has sense only if space is not an issue.

The code would look something like this:

public class BST {
    private Node root;

    public BST(){
        root = new Node();
    }

    public void insert(int elem){
        root.insert(elem);
    }

    public void delete(int elem){
        root.delete(elem);
    }
}

public class Node{
    private static final int EMPTY_VALUE = /* your empty value */;
    private int element;
    private Node parent;
    private Node left;
    private Node right;

    public Node(){
        this(EMPTY_VALUE, null, null, null);
    }

    public Node(int elem, Node p, Node l, Node r){
        element = elem;
        parent = p;
        left = l;
        right = r;
    }

    public void insert(int elem){
        Node thisNode = this;

        // this cycle goes on until an empty node is found
        while(thisNode.element != EMPTY_VALUE){
            // follow the correct path for the insertion here
        }

        // insert new element here
        // thisNode is an empty node at this point
        thisNode.element = elem;
        thisNode.left = new Node();
        thisNode.right = new Node();
        thisNode.left.parent = thisNode;
        thisNode.right.parent = thisNode;
    }

    public void delete(int elem){
        // manage delete here
    }
}

Upvotes: 0

Programmer Person
Programmer Person

Reputation: 618

The null node itself is the sentinel, but instead of using null, you can use an instance of a Node with a special flag (or a special subclass), which is effectively the null node. A Nil node makes sense, as that is actually a valid tree: empty!

And by using recursion you can get rid of the extra checks and new Node littered all over (which is what I presume is really bothering you).

Something like this:

class Node {
  private Value v;
  private boolean is_nil;
  private Node left;
  private Node right;

  public void insert(Value v) {
    if (this.is_nil) {
      this.left = new Node(); // Nil node
      this.right = new Node(); // Nil node
      this.v = v;
      this.is_nil = false;
      return;
    }
    if (v > this.v) {
      this.right.insert(v);
    } else {
      this.left.insert(v);
    }
  }
}

class Tree {
  private Node root;
  public Tree() {
    root = new Node(); // Nil Node.
  }
  public void insert(Value v) {
    root.insert(v);
  }
}

If you don't want to use recursion, your while(true) is kind of a code smell.

Say we keep it as null, we can perhaps refactor it as.

public void insert(Value v) {
  prev = null;
  current = this.root;
  boolean left_child = false;
  while (current != null) {
    prev = current;
    if (v > current.v) {
      current = current.right;
      left_child = false;
    } else {
      current = current.left;
      left_child = true;
    }
  }
  current = new Node(v);
  if (prev == null) {
    this.root = current;
    return;
  }
  if (left_child) {
    prev.left = current;
  } else {
    prev.right = current;
  }
}

Upvotes: 1

Related Questions