Basis of data structure: Java version hand torn BST binary search tree (including pre order, middle order, post order, sequence traversal, and tree height calculation)

Prequel

The graphical presentation in the article can be simulated at the following website:
https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

1, BST overview

Like linked list, binary search tree is also a dynamic data structure. But its query efficiency is higher, and the average sharing time complexity of query is O(logn).

Features of binary search tree:

  1. There is only one root node, each node has at most two child nodes, and each node has at most one parent node.
  2. Binary search tree has a natural recursive structure. The left / right subtree of each node is also a binary search tree.
  3. The value of each node of the binary search tree is greater than that of all nodes of the left subtree and less than that of all nodes of the right subtree.
  4. The stored elements must be Comparable, that is, they implement the Comparable interface.
  5. In extreme cases, BST will become a linked list structure.
  6. Binary search trees can support duplicate elements.

Traversal of binary search tree:

  • Preorder traversal: follow node – left child node – right child node
  • Middle order traversal: left child node - root node - right child node
    The sorted results of the binary search tree.
  • Post order traversal: left child node - right child node - following node

The version of our code does not allow the insertion of duplicate elements. As shown in the following figure, the version that supports repeated data insertion simply creates a new node on the right subtree of an existing node.

Deleting duplicate nodes is to delete the first found node.

2, Code implementation

1. Tree common interface

package com.saint.base.datastructure.tree;

/**
 * Tree interface
 *
 * @author Saint
 * @version 1.0
 * @createTime 2021-09-09 8:43
 */
public interface Tree<E> {

    /**
     * Number of elements
     *
     * @return int
     */
    int size();

    /**
     * Add element
     *
     * @param e
     */
    void add(E e);

    /**
     * Delete element
     *
     * @param e
     */
    void remove(E e);

    /**
     * Does the tree contain the element e
     *
     * @param e
     * @return boolean
     */
    boolean contains(E e);

}

2. Tree node

package com.saint.base.datastructure.tree;

/**
 * Tree node
 *
 * @author Saint
 * @version 1.0
 * @createTime 2021-09-09 8:43
 */
public class TreeNode<E> {

    /**
     * For storing data
     */
    public E e;

    /**
     * Left child node
     */
    public TreeNode left;

    /**
     * Right child node
     */
    public TreeNode right;

    public TreeNode(E e) {
        this.e = e;
        this.left = null;
        this.right = null;
    }
}

3. BST implementation

package com.saint.base.datastructure.tree;

import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;

/**
 * Binary search tree
 *
 * @author Saint
 */
public class BSTree<E extends Comparable<E>> implements Tree<E> {

    /**
     * Tree container
     */
    TreeNode<E> root;

    /**
     * Number of tree nodes
     */
    int size;

    public BSTree() {
        root = null;
        size = 0;
    }

    @Override
    public void add(E e) {
        root = add(root, e);
    }

    /**
     * Adds a node to the specified tree
     *
     * @param node
     * @param e
     * @return
     */
    private TreeNode<E> add(TreeNode<E> node, E e) {
        // If the tree node is null, create a new node
        if (null == node) {
            size++;
            return new TreeNode<>(e);
        }

        // If the data to be added is smaller than the root node, it is inserted into the left subtree of the node
        if (e.compareTo(node.e) < 0) {
            node.left = add(node.left, e);
        } else if (e.compareTo(node.e) > 0) {
            node.right = add(node.right, e);
        } else {
            // Do nothing. When the value of E is equal to that of node.e, no operation will be done. Here you can customize the operation
        }

        // Recursively returns the current following node
        return node;
    }

    @Override
    public void remove(E e) {
        root = remove(root, e);
    }

    private TreeNode<E> remove(TreeNode<E> node, E e) {
        if (null == node) {
            throw new RuntimeException("BST is empty!");
        }
        // If the value of the node to be deleted is smaller than the value of the node node
        if (e.compareTo(node.e) < 0) {
            node.left = remove(node.left, e);
            return node;
        } else if (e.compareTo(node.e) > 0) {
            // If the node value to be deleted is greater than the node node value
            node.right = remove(node.right, e);
            return node;
        } else {
            // The node to be deleted is the current node
            // The following three cases should be considered: the left subtree of node is null, the right subtree of node is null, and the left and right subtrees of node are not null.

            // 1) The left subtree of node is null
            if (node.left == null) {
                TreeNode<E> rightNode = node.right;
                node.right = null;
                size--;
                return rightNode;
            }

            // 2) The right subtree of node is null
            if (node.right == null) {
                TreeNode<E> leftNode = node.left;
                node.left = null;
                size--;
                return leftNode;
            }

            // 3) Neither left nor right subtree of node is null

            // Our ideas are as follows: 1. Obtain the minimum value of the right subtree of node as the root node newNode of the new tree. 2
            // 2. Then delete the minimum value in the right subtree of node from the right subtree.
            // 3. Then hang the tree structures of node.left and node.right (after removing the minimum value) on the left and right subtrees of newNode respectively.
            TreeNode<E> newNode = minimum(node.right);
            // size is specified in the removeMin() method--
            TreeNode<E> rightNode = removeMin(node.right);
            newNode.left = node.left;
            node.left = null;
            node.right = null;
            newNode.right = rightNode;
            return newNode;
        }
    }

    /**
     * Remove the minimum value from the node tree
     *
     * @param node
     * @return
     */
    public TreeNode<E> removeMin(TreeNode<E> node) {
        if (node.left == null) {
            TreeNode<E> rightNode = node.right;
            node.right = null;
            size--;
            return rightNode;
        }
        return removeMin(node.left);
    }

    /**
     * Remove the maximum value of the node tree
     *
     * @param node
     * @return
     */
    public TreeNode<E> removeMax(TreeNode<E> node) {
        if (null == node.right) {
            TreeNode<E> leftNode = node.left;
            node.left = null;
            size--;
            return leftNode;
        }
        return removeMax(node.right);
    }


    @Override
    public boolean contains(E e) {
        return contains(root, e);
    }

    private boolean contains(TreeNode<E> node, E e) {
        if (null == node) {
            return false;
        }
        if (e.compareTo(node.e) < 0) {
            return contains(node.left, e);
        } else if (e.compareTo(node.e) > 0) {
            return contains(node.right, e);
        }
        return true;
    }

    /**
     * Gets the maximum value of the BST tree
     *
     * @return
     */
    public E maximum() {
        if (size == 0) {
            throw new RuntimeException("BST is empty!");
        }
        return maximum(root).e;
    }

    private TreeNode<E> maximum(TreeNode<E> node) {
        if (node.right == null) {
            return node;
        }
        return maximum(node.right);
    }


    /**
     * Gets the minimum value of the tree
     *
     * @return
     */
    public E minimum() {
        if (size == 0) {
            throw new RuntimeException("BST is empty!");
        }
        return minimum(root).e;
    }

    private TreeNode<E> minimum(TreeNode<E> node) {
        if (node.left == null) {
            return node;
        }
        return minimum(node.left);
    }

    @Override
    public int size() {
        return size;
    }

    /**
     * Tree height
     *
     * @return
     */
    public void height() {
        System.out.println("----Tree height----");
        System.out.println("DFS Method:" + getHeightDFS(root));
        System.out.println("BFS Method:" + getHeightBFS(root));
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        generateBSTString(root, 0, res);
        return res.toString();
    }

    private void generateBSTString(TreeNode<E> root, int depth, StringBuilder res) {

        if (root == null) {
            return;
        }
        res.append(generateDepthString(depth) + root.e + "\n");
        generateBSTString(root.left, depth + 1, res);
        generateBSTString(root.right, depth + 1, res);
    }

    private String generateDepthString(int depth) {
        StringBuilder res = new StringBuilder();
        for (int i = 0; i < depth; i++) {
            res.append("--");
        }
        return res.toString();
    }

}

4. Tree traversal – DFS

Many operations of the tree are based on tree traversal. Because the pre order traversal rules of the tree are left and right, we have two methods for pre order traversal: recursive and non recursive. The middle order traversal and subsequent traversal can only be recursive.

/**
 * BST Preorder traversal of -- follow left and right
 */
public void preOrder() {
    System.out.println("Recursive preorder traversal");
    preOrder(root);
    System.out.println();
    System.out.println("Non recursive preorder traversal:");
    preOrderNR(root);
    System.out.println();

}

/**
 * Recursive preorder traversal
 *
 * @param node
 */
private void preOrder(TreeNode<E> node) {
    if (null == node) {
        return;
    }
    // The output here can be replaced by business logic.
    System.out.print(node.e + " ");
    preOrder(node.left);
    preOrder(node.right);
}

/**
 * Preorder traversal in non recursive way
 *
 * @param root
 */
private void preOrderNR(TreeNode<E> root) {
    Stack<TreeNode<E>> stack = new Stack<>();
    stack.push(root);
    while (!stack.isEmpty()) {
        TreeNode<E> popNode = stack.pop();

        // The output here can be replaced by business logic.
        System.out.print(popNode.e + " ");

        // Because the stack is first in and last out, and the preorder traversal is left and right, we first put the right subtree of the current node into the stack.
        if (popNode.right != null) {
            stack.push(popNode.right);
        }
        if (popNode.left != null) {
            stack.push(popNode.left);
        }
    }
}

/**
 * BST Middle order traversal of -- left and right
 */
public void inOrder() {
    System.out.println("Recursive middle order traversal:");
    inOrder(root);
    System.out.println();

}

/**
 * Recursive middle order traversal
 *
 * @param node
 */
private void inOrder(TreeNode<E> node) {
    if (null == node) {
        return;
    }
    inOrder(node.left);
    // The output here can be replaced by business logic.
    System.out.print(node.e + " ");
    inOrder(node.right);
}

/**
 * BST Postorder traversal of -- left and right roots
 */
public void postOrder() {
    System.out.println("Recursive postorder traversal:");
    postOrder(root);
    System.out.println();

}

/**
 * Recursive postorder traversal
 *
 * @param node
 */
private void postOrder(TreeNode<E> node) {
    if (null == node) {
        return;
    }
    postOrder(node.left);
    postOrder(node.right);
    // The output here can be replaced by business logic.
    System.out.print(node.e + " ");
}

5. Tree traversal – BFS

In fact, the recursive way of preorder traversal of the tree can also be regarded as a special BFS,
The gist of BFS is to traverse the tree layer by layer.

/**
 * Breadth traversal
 */
public void levelOrder() {
    System.out.println("Breadth traversal:");
    Queue<TreeNode<E>> queue = new LinkedList<>();
    queue.add(root);
    while (!queue.isEmpty()) {
        TreeNode<E> cur = queue.remove();
        System.out.print(cur.e + " ");
        if (cur.left != null) {
            queue.add(cur.left);
        }
        if (cur.right != null) {
            queue.add(cur.right);
        }
    }
}

6. Tree height

We can use DFS or BFS to obtain the height of the tree. DFS only needs to find the deepest vertical node of the path (from top to bottom); BFS records the total number of layers when traversing layer by layer.

DFS:

/**
 * DFS -- Gets the height of the tree
 *
 * @param node
 * @return
 */
public int getHeightDFS(TreeNode<E> node) {
    if (null == node) {
        return 0;
    }
    return Math.max(getHeightDFS(node.left), getHeightDFS(node.right)) + 1;
}

BFS:

/**
 * BFS -- Gets the height of the tree
 *
 * @param node
 * @return
 */
public int getHeightBFS(TreeNode<E> node) {
    Queue<TreeNode<E>> queue = new LinkedList<>();
    queue.add(node);

    // Tree height
    int height = 0;
    // A queue used to traverse each layer of data
    Queue<TreeNode<E>> temp;
    while (!queue.isEmpty()) {
        temp = new LinkedList<>();
        for (TreeNode<E> cur : queue) {
            if (cur.left != null) {
                temp.add(cur.left);
            }
            if (cur.right != null) {
                temp.add(cur.right);
            }
        }
        height++;
        queue = temp;
    }
    return height;
}

7. Testing

package com.saint.base.datastructure.tree;

/**
 * @author Saint
 */
public class BSTreeTest {

    public static void main(String[] args) {
        BSTree<Integer> bsTree = new BSTree<>();
        bsTree.add(4);
        System.out.println("after add, root is : \n" + bsTree.toString());
        System.out.println();
        bsTree.add(2);
        System.out.println("after add, root is : \n" + bsTree.toString());
        System.out.println();
        bsTree.add(6);
        System.out.println("after add, root is : \n" + bsTree.toString());
        System.out.println();
        bsTree.add(1);
        System.out.println("after add, root is : \n" + bsTree.toString());
        System.out.println();
        bsTree.add(3);
        System.out.println("after add, root is : \n" + bsTree.toString());
        System.out.println();
        bsTree.add(5);
        System.out.println("after add, root is : \n" + bsTree.toString());
        System.out.println();
        bsTree.add(7);
        System.out.println("after add, root is : \n" + bsTree.toString());
        System.out.println();
        bsTree.add(8);
        System.out.println("after add, root is : \n" + bsTree.toString());

        // Preorder traversal
        bsTree.preOrder();

        // Medium order traversal
        bsTree.inOrder();

        // Postorder traversal
        bsTree.postOrder();

        // Breadth traversal
        bsTree.levelOrder();

        // Tree height
        bsTree.height();
        
        // Delete a node with both left and right subtrees
        bsTree.remove(6);
        System.out.println("after remove, root is : \n" + bsTree.toString());

    }
}

The tree structure is:

1) When we delete node 006:
Our deleted node version is also different from the deletion of the website. It directly takes the maximum value of the left subtree as the root node, while we take the minimum value of the right subtree as the root node. Interested old friends can implement one by themselves. We already have the basic methods.


In addition, the centralized deletion method is Goo Goo

Let's simply say:

bsTree.remove(1);
// Delete the node whose right subtree is not null
bsTree.remove(3);
bsTree.remove(1);
// Delete the node whose left subtree is not null
bsTree.remove(2);

Console output:

after add, root is : 
4


after add, root is : 
4
--2


after add, root is : 
4
--2
--6


after add, root is : 
4
--2
----1
--6


after add, root is : 
4
--2
----1
----3
--6


after add, root is : 
4
--2
----1
----3
--6
----5


after add, root is : 
4
--2
----1
----3
--6
----5
----7


after add, root is : 
4
--2
----1
----3
--6
----5
----7
------8

Recursive preorder traversal
4 2 1 3 6 5 7 8 
Non recursive preorder traversal:
4 2 1 3 6 5 7 8 
Recursive middle order traversal:
1 2 3 4 5 6 7 8 
Recursive postorder traversal:
1 3 2 5 8 7 6 4 
Breadth traversal:
4 2 6 1 3 5 7 8 ----Tree height----
DFS Mode: 4
BFS Mode: 4
after remove, root is : 
4
--2
----1
----3
--7
----5
----8

Embodied in LeetCode

Calculate the height of the tree; DFS and BFS traversal of data; The first, middle and last order traversal of the tree is reflected in leetcode:
Depth of binary tree
Middle order traversal of binary tree
Binary Tree Postorder Traversal
Sequence traversal of binary tree

In the advanced version, you can try to do the following three questions yourself:

Tags: Java data structure Binary tree

Posted on Fri, 19 Nov 2021 13:36:02 -0500 by Shad