java implementation of binary search tree and its related operations

Keywords: Java Algorithm data structure linked list

java implementation of binary search tree and its related operations

The basic operations of Binary Search Tree include search, maximum, minimum, precursor, successor, insertion and deletion.

The time spent on the basic operation of the binary search tree is proportional to the height of the tree. For example, for a complete binary tree with n nodes, the time complexity of the basic operation on it is O(logn). However, if the tree is a linear chain with n nodes, the time complexity in this case is O(n).

1. What is a binary lookup tree

Binary search tree is an organized binary tree. We can represent such a tree by linking nodes. Each node contains five attributes: key, data, left, right, and parent.

If a node has no parent node or a child node, the value of its corresponding attribute is null.

Create a Node.java file to represent the node with the following code:

public class Node {
    public int key;
    public int data;
    public Node left;
    public Node right;
    public Node parent;
    
    public Node(){}
    
    public Node(int key) {
        this.key = key;
    }
}

Create a BinarySearchTree.java file to represent the binary search tree with the following code:

public class BinarySearchTree {
    public Node root;
}

Next, the code will be supplemented step by step.

For keys stored in binary search tree, the following conditions must be met (characteristics of binary search tree):

For any node n in the binary tree, if left is any node in the left subtree of N, there is left.key < = n.key; If right is any node in n right subtree, there is n.key < = right.key.

If we traverse the binary search tree in middle order, we can print all the key s in ascending order.

The following code can be added to BinarySearchTree.java file to realize medium order traversal:

 private void innerWalk(Node node) {
        if(node != null) {
            innerWalk(node.left);
            System.out.print(node.key + " ");
            innerWalk(node.right);
        }
    }

    public void innerWalk() {
        this.innerWalk(this.root);
        System.out.println();
    }

2. Query binary search tree

2.1. Search

Search for the node with key in the binary search tree. If it is found, it returns the node; otherwise, it returns null. It can be found by using the characteristics of binary search tree.

You can add the following code to BinarySearchTree.java file to realize search:

 public Node search(int key) {
        Node pointer = this.root;
        while (pointer != null && pointer.key != key) {
            pointer = key < pointer.key ? pointer.left : pointer.right;
        }
        return pointer;
    }

2.2 minimum and maximum values

The node with the smallest key can be traversed and tracked from the root node to the last one according to the characteristics of the binary tree.

The following code can be added to the Node.java file:

  public Node minimum() {
        Node pointer = this;
        while(pointer.left != null){
            pointer = pointer.left;
        }
        return pointer;
    }

The node with the largest key can be traversed and tracked from the root node to the right child node according to the characteristics of the binary tree.

The following code can be added to the Node.java file:

 public Node maximum() {
        Node pointer = this;
        while(pointer.right != null) {
            pointer = pointer.right;
        }
        return pointer;
    }

Add the following code to BinarySearchTree.java file:

  public Node minimum() {
        return this.root.maximum();
    }

2.3 precursors and successors

Given a node in a binary search tree, sometimes we need to find its direct successor (the first one behind) when all nodes in the tree are sorted in ascending order.

If all keys in the tree are different, the direct successor of node n is the smallest node greater than n.key.

If the node has a right node, its direct successor is the node with the smallest right subtree key. If the node is, it is judged whether the node is the left child node or the right child node of its parent node.

If it is a left child node, its parent node is returned. If it is a right child node, judge whether its parent node is the left child node or the right child node of its parent node, and so on.

null if not found.

The following implementation code can be added to the Node.java file:

public Node successor() {
        Node pointer = this;
        if (pointer.right != null)
            return pointer.right.minimum();
        Node parentPointer = pointer.parent;
        while (parentPointer != null && parentPointer.right == pointer) {
            pointer = parentPointer;
            parentPointer = parentPointer.parent;
        }
        return pointer;
    }

The direct precursor of a node refers to the first in front of the node when all nodes in the tree are sorted in ascending order. Seeking the direct precursor and seeking the direct successor are symmetrical.

The following implementation code can be added to the Node.java file:

  public Node predecessor() {
        Node pointer = this;
        if (pointer.left != null)
            return pointer.left.maximum();
        Node parentPointer = pointer.parent;
        while (parentPointer != null && parentPointer.left == pointer) {
            pointer = parentPointer;
            parentPointer = parentPointer.parent;
        }
        return pointer;
    }

3. Insert and delete

Insertion and deletion will change the binary search tree, but its characteristics must be maintained.

3.1 insertion

Find a key in the tree that is closest to the node where you want to insert the node key, and there is a location where you can insert it, and then insert it in the appropriate location.

The following implementation code can be added to BinarySearchTree.java file:

  public void insert(Node node) {
        Node pointer = this.root;
        Node parentPointer = null;
        while (pointer != null) {
            parentPointer = pointer;
            pointer = node.key < pointer.key ? pointer.left : pointer.right;
        }
        node.parent = parentPointer;
        if (parentPointer == null)
            this.root = node;
        else if (node.key < parentPointer.key) {
            parentPointer.left = node;
        } else {
            parentPointer.right = node;
        }
    }

3.2. Delete

After deleting nodes, we should maintain the characteristics of binary search tree.

If you want to delete a node without any child nodes, you can delete it directly without any processing.

If the node to be deleted has only one left subtree, the root node of the left subtree can be used to replace the node to be deleted, or the node with the largest key in the left subtree can be used to replace the node to be deleted.

If the node to be deleted has only one right subtree, the root node of the right subtree can be used to replace the node to be deleted, or the node with the smallest key in the right subtree can be used to replace the node to be deleted.

If the node to be deleted has both a left subtree and a right subtree, the node with the largest key in the left subtree can be used to replace the node to be deleted, or the node with the smallest key in the right subtree can be used to replace the node to be deleted.

Suppose you want to delete a node from the binary lookup tree, the specific steps of a deletion method are as follows:

If the node has no left child node, then we can replace the node with the node.right point of the right child node. Node.right may or may not be null.

If the node has only the left child node.left, we use node.left instead of node.

If a node has a left child node.left and a right child node.right. We need to find the direct successor s of node (the node with the smallest key in the right subtree of node, which has no left child node), s in the right subtree of node, and then replace node with S. The following two situations should be considered:

If s is the right child node of node, s can be used instead of node.

Otherwise, replace s with the right child node of S, and then replace node with S.

Because we need to replace the subtree in the binary search tree, we can write a method to replace the subtree first.

The following implementation code can be added to BinarySearchTree.java file:

  /**
     * Replacing node1 with node2
     * 
     * @param node1
     * @param node2
     */
    private void transplant(Node node1, Node node2) {
        if (node1.parent == null) {
            this.root = node2;
        } else if (node1.parent.left == node1) {
            node1.parent.left = node2;
        } else {
            node1.parent.right = node2;
        }

        if (node2 != null)
            node2.parent = node1.parent;
        node1.parent = null;
    }

Next, delete the code implementation of the node operation.

The following implementation code can be added to BinarySearchTree.java file:

  public void delete(Node node) {
        if (node.left == null) {
            this.transplant(node, node.right);
        } else if (node.right == null) {
            this.transplant(node, node.left);
        } else {
            Node successor = node.successor();
            if (successor.parent != node) {
                this.transplant(successor, successor.right);
                successor.right = node.right;
                successor.right.parent = successor;
            }
            this.transplant(node, successor);
            successor.left = node.left;
            successor.left.parent = successor;
        }
    }

4. Complete code

Node.java file

public class Node {
    public int key;
    public int data;
    public Node left;
    public Node right;
    public Node parent;

    public Node() {
    }

    public Node(int key) {
        this.key = key;
    }

    public Node minimum() {
        Node pointer = this;
        while (pointer.left != null)
            pointer = pointer.left;
        return pointer;
    }

    public Node maximum() {
        Node pointer = this;
        while (pointer.right != null)
            pointer = pointer.right;
        return pointer;
    }

    public Node successor() {
        Node pointer = this;
        if (pointer.right != null)
            return pointer.right.minimum();
        Node parentPointer = pointer.parent;
        while (parentPointer != null && parentPointer.right == pointer) {
            pointer = parentPointer;
            parentPointer = parentPointer.parent;
        }
        return pointer;
    }

    public Node predecessor() {
        Node pointer = this;
        if (pointer.left != null)
            return pointer.left.maximum();
        Node parentPointer = pointer.parent;
        while (parentPointer != null && parentPointer.left == pointer) {
            pointer = parentPointer;
            parentPointer = parentPointer.parent;
        }
        return pointer;
    }
}

BinarySearchTree.java file

public class BinarySearchTree {
    public Node root;

    private void innerWalk(Node node) {
        if (node != null) {
            innerWalk(node.left);
            System.out.print(node.key + " ");
            innerWalk(node.right);
        }
    }

    public void innerWalk() {
        this.innerWalk(this.root);
        System.out.println();
    }

    public Node search(int key) {
        Node pointer = this.root;
        while (pointer != null && pointer.key != key) {
            pointer = key < pointer.key ? pointer.left : pointer.right;
        }
        return pointer;
    }

    public Node minimum() {
        return this.root.minimum();
    }

    public Node maximum() {
        return this.root.maximum();
    }

    public void insert(Node node) {
        Node pointer = this.root;
        Node parentPointer = null;
        while (pointer != null) {
            parentPointer = pointer;
            pointer = node.key < pointer.key ? pointer.left : pointer.right;
        }
        node.parent = parentPointer;
        if (parentPointer == null)
            this.root = node;
        else if (node.key < parentPointer.key) {
            parentPointer.left = node;
        } else {
            parentPointer.right = node;
        }
    }

    /**
     * Replacing node1 with node2
     * 
     * @param node1
     * @param node2
     */
    private void transplant(Node node1, Node node2) {
        if (node1.parent == null) {
            this.root = node2;
        } else if (node1.parent.left == node1) {
            node1.parent.left = node2;
        } else {
            node1.parent.right = node2;
        }

        if (node2 != null)
            node2.parent = node1.parent;
        node1.parent = null;
    }

    public void delete(Node node) {
        if (node.left == null) {
            this.transplant(node, node.right);
        } else if (node.right == null) {
            this.transplant(node, node.left);
        } else {
            Node successor = node.successor();
            if (successor.parent != node) {
                this.transplant(successor, successor.right);
                successor.right = node.right;
                successor.right.parent = successor;
            }
            this.transplant(node, successor);
            successor.left = node.left;
            successor.left.parent = successor;
        }
    }
}

5. Demonstration

Demo code:

public class Test01 {
    public static void main(String[] args) {
        Node n1 = new Node(1);
        Node n2 = new Node(2);
        Node n3 = new Node(3);
        Node n4 = new Node(4);
        Node n5 = new Node(5);

        BinarySearchTree bst = new BinarySearchTree();
        bst.insert(n3);
        bst.insert(n4);
        bst.insert(n2);
        bst.insert(n1);
        bst.insert(n5);

        System.out.println("bst.minimum().key: " + bst.minimum().key);
        System.out.println("bst.maximum().key: " + bst.maximum().key);
        System.out.println("n3.successor().key: " + n3.successor().key);
        System.out.println("n3.predecessor().key: " + n3.predecessor().key);
        System.out.println("bst.search(4).key: " + bst.search(4).key);

        System.out.print("tree: ");
        bst.innerWalk();
        System.out.print("delete n3: ");
        bst.delete(n3);
        bst.innerWalk();
        System.out.print("delete n2: ");
        bst.delete(n2);
        bst.innerWalk();
        System.out.print("delete n1: ");
        bst.delete(n1);
        bst.innerWalk();
        System.out.print("delete n4: ");
        bst.delete(n4);
        bst.innerWalk();
        System.out.print("delete n5: ");
        bst.delete(n5);
        bst.innerWalk();
    }
}

result:

bst.minimum().key: 1
bst.maximum().key: 5
n3.successor().key: 4
n3.predecessor().key: 2
bst.search(4).key: 4
tree: 1 2 3 4 5 
delete n3: 1 2 4 5 
delete n2: 1 4 5 
delete n1: 4 5 
delete n4: 5 
delete n5: 

Posted by alwaysme on Sun, 31 Oct 2021 05:13:56 -0700