Finally, I tore the binary search tree by hand (the code can be run with detailed comments)

preface

This paper contains the common methods of operating binary search tree, focusing on the deletion of binary search tree. Deletion is far more complex than insertion. There are many cases on the Internet that are not well considered. In a rage, I tore a Demo with detailed comments.

Definition and properties of binary search tree

Binary search tree (also called binary search tree, or Binary Sort Tree)

Definition (from Baidu Encyclopedia):

Binary Search Tree (also: Binary Search Tree, binary sort tree) is either an empty tree or a binary tree with the following properties: if its left subtree is not empty, the values of all nodes on the left subtree are less than the values of its root node; If its right subtree is not empty, the values of all nodes on the right subtree are greater than those of its root node; Its left and right subtrees are also binary sort trees. As a classical data structure, Binary Search Tree not only has the characteristics of fast insertion and deletion of linked list, but also has the advantage of fast search of array; Therefore, it is widely used. For example, this data structure is generally used in file system and database system for efficient sorting and retrieval.

nature

  • Traversing the binary search tree in order can make the nodes orderly!
  • If you insert and delete nodes, you must ensure that the whole tree is still a binary search tree after insertion and deletion.
  • How to find the node with the smallest keyword: it is the last node that can be reached by recursively traversing the left subtree
  • How to find the node with the largest keyword: it is the last node that can be reached by recursively traversing the right subtree

Use Java code to realize the definition of binary search tree:

public static class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;

        //Constructor
        TreeNode() {
        }

        TreeNode(int val) {
            this.val = val;
        }

        TreeNode(int val, TreeNode left, TreeNode right) {
            this.val = val;
            this.left = left;
            this.right = right;
        }
    }

Convert array to binary search tree

What goes in is an array of binary search tree structure, and what comes out is a binary search tree (sequence insertion)

   For example, the input parameters are: [3,9,20,null,null,15,7]

    Then the return value is the binary tree structure as shown in the following figure:
        3
       / \
      9  20
        /  \
       15   7
public static TreeNode initBinaryTree(Integer[] arr) {
        if (arr.length == 0){   //Boundary check
            return null;
        }
        //Create a new TreeNode as the root node
        TreeNode treeNode = new TreeNode(arr[0], null, null);
        //Use LinkedList to simulate Queue. add is enqueue, remove is dequeue, and peak is to get the Queue header element
        Queue<TreeNode> queue = new LinkedList<>();
        int i = 1;
        queue.add(treeNode);
        while (i < arr.length){
            TreeNode peek = queue.peek();   //Current queue header element
            if (arr[i]!=null){
                peek.left = new TreeNode(arr[i],null,null);
                queue.add(peek.left);   //enqueue
            }else {
                peek.left = null;   //If it is empty, insert it directly without enqueue
            }
            i++;
            if (i >= arr.length){
                break;
            }
            if (arr[i]!=null){
                peek.right = new TreeNode(arr[i],null,null);
                queue.add(peek.right);  //enqueue
            }else {
                peek.right = null;   //If it is empty, insert it directly without enqueue
            }
            i++;
            queue.remove(); //dequeue
        }
        return treeNode;
    }

Insertion of binary search tree:

Compared with deletion, the insertion of binary search tree is very simple.

Although the inserted node needs to maintain the nature of the binary search tree, it is still a binary search tree. However, the observation shows that there is no need to adjust the structure of the binary search tree. It only needs to be inserted into the bottom of the node, which is still a binary search tree

public TreeNode insertIntoBST(TreeNode root, int val) {
        if (root == null) {
            return new TreeNode(val);
        }
        TreeNode pos = root;
        while (pos != null) {
            if (val < pos.val) {
                if (pos.left == null) {
                    pos.left = new TreeNode(val);
                    break;
                } else {
                    pos = pos.left;
                }
            } else {
                if (pos.right == null) {
                    pos.right = new TreeNode(val);
                    break;
                } else {
                    pos = pos.right;
                }
            }
        }
        return root;
    }

Delete binary search tree:

The deletion of binary search tree is quite complex. We must dynamically adjust the structure of binary search tree to make it still be a binary search tree after deleting nodes!

First, some pre knowledge and concepts are required:

Predecessor node and successor node

In the deletion of binary search tree, there are successor nodes (recorded as success) and predecessor nodes (recorded as predecessor). It is defined as:

The success of the node: the binary search tree traverses according to the middle order (in fact, it is arranged from small to large), and the number on the right of the node. (in fact, it is the first number larger than the node after arranging from small to large)
predecessor of the node: the binary search tree traverses according to the middle order (in fact, it is arranged from small to large), and the number on the left of the node. (in fact, it is the first number smaller than the node after arranging from small to large)

Nodes can be deleted in three ways

  • A: If the node is a leaf node, you can delete it directly
  • B: If the node has a right node, the successor node of the node (recorded as success) is mentioned to the location of the node to be overwritten.

    After overwriting, the successor repeats. So find a way to delete the successor (recursively call 2 or 3 to delete it)
    In this process, the deletion operation can be completed.
  • C: If the node has only a left node but no right node, its successor node must be above it (determined by the nature of binary search tree). Therefore, we can use the predecessor node (recorded as predecessor) of the node to mention the location of the node for coverage.

    After overwriting, the predecessor repeats. So find a way to delete the predecessor (recursively call 2 or 3 to delete it)
    In this process, the deletion operation can be completed.

The official solution to question 450 of LeetCode also reflects the above ideas:

https://leetcode-cn.com/problems/delete-node-in-a-bst/solution/shan-chu-er-cha-sou-suo-shu-zhong-de-jie-dian-by-l/


Knowing the above concepts, you can start writing code. The implementation of the code is also complex. I will split it into several sub functions to implement them respectively.

How to find the successor and predecessor of the node more conveniently without traversing the binary tree in middle order?

In fact, it is very simple. Finding success is to find the right node (if any) of the node, regard the right node of the node as a new binary search tree, and then find the smallest node of the binary search tree, which is success.
It embodies the idea of finding the "smallest node larger than me"

To find the predecessor is just the opposite, that is, to find the left node (if any) of the node, regard the left node of the node as a new binary search tree, and then find the largest node of the binary search tree, which is the predecessor
It embodies the idea of finding "the largest node smaller than me"

Note: this method finds the winner and predecessor, which is only applicable to the presence of left / right nodes. If it does not exist, please honestly use middle order traversal!
Note: this method finds the winner and predecessor, which is only applicable to the presence of left / right nodes. If it does not exist, please honestly use middle order traversal!
Note: this method finds the winner and predecessor, which is only applicable to the presence of left / right nodes. If it does not exist, please honestly use middle order traversal!

The Java code is given directly below:

private  TreeNode findSuccessor(TreeNode node) {
        if (node.right == null) {  //If there is no right node, the success of the node must be on it, so it can't be found with this method.
            return null;
        }
        node = node.right;
        //The following code is actually calling the findMin method to find the smallest node
        while (true){
            if (node.left != null) {
                node = node.left;
            }else {
                break;
            }
        }
        return node;
    }

    private  TreeNode findPredecessor(TreeNode node) {
        if (node.left == null) {
            return null;
        }
        node = node.left;
        //The following code is actually calling the findMax method to find the largest node
        while (true){
            if (node.right != null) {
                node = node.right;
            }else {
                break;
            }
        }
        return node;
    }

Finally, delete the node code:

public class DeleteBST {
    /*public TreeNode deleteNode(TreeNode root, int key) {

    }*/

    public  TreeNode deleteNode(TreeNode root, int key) {
        //Find this node first. If not found, return to the original tree directly
        TreeNode workNode = root;    //Tool node
        TreeNode targetNode = null;  //The node found
        while (workNode != null) {
            if (key < workNode.val) {
                workNode = workNode.left;
            } else if (key > workNode.val) {
                workNode = workNode.right;
            } else { //root.val == key
                targetNode = workNode;  //The node has been found
                break;
            }
        }
        if (targetNode == null) {    //The binary tree does not have this node at all. You can directly return to the original tree
            return root;
        }
        //Case 1: this node is a leaf node and can be deleted directly
        if (targetNode.left==null && targetNode.right==null){
            if (targetNode.val == root.val){    //Boundary check: the root node is the leaf node. return null directly
                return null;
            }
            deleteLeaf(root,targetNode);
            return root;
        }
        //Case 2: if the node has a right node, the position of the node's successor node (recorded as success) mentioned to the node is overwritten and deleted recursively
        if (targetNode.right!=null){
            TreeNode successor = findSuccessor(targetNode);
            coverNode(targetNode,successor);
            targetNode.right = deleteNode(targetNode.right, successor.val);
        }else if (targetNode.right==null && targetNode.left!=null){ //Case 3: if the node has only a left node but no right node, use the location of the node mentioned by the precursor node (recorded as predecessor) of the node to overwrite.
            TreeNode predecessor = findPredecessor(targetNode);
            coverNode(targetNode,predecessor);
            targetNode.left =  deleteNode(targetNode.left, predecessor.val);
        }



        return root;
    }

    //Overlay node. oriNode refers to the node to be covered, and newNode refers to the node that replaces it
    private  void coverNode(TreeNode oriNode, TreeNode newNode) {
        oriNode.val = newNode.val;
    }

    //Delete leaf node directly
    private  void deleteLeaf(TreeNode root,TreeNode targetNode) {
        TreeNode workNode = root;    //Tool node
        while (true){
            if (targetNode.val < workNode.val){
                if (workNode.left.val!= targetNode.val){
                    workNode = workNode.left;
                }else {
                    workNode.left = null;   //If equal, delete the node
                    return;
                }
            }else {
                if (workNode.right.val!= targetNode.val){
                    workNode = workNode.right;
                }else {
                    workNode.right = null;   //If equal, delete the node
                    return;
                }
            }
        }
    }

    private  TreeNode findSuccessor(TreeNode node) {
        if (node.right == null) {  //If there is no right node, the success of the node must be on it, so it can't be found with this method.
            return null;
        }
        node = node.right;
        //The following code is actually calling the findMin method to find the smallest node
        while (true){
            if (node.left != null) {
                node = node.left;
            }else {
                break;
            }
        }
        return node;
    }

    private  TreeNode findPredecessor(TreeNode node) {
        if (node.left == null) {
            return null;
        }
        node = node.left;
        //The following code is actually calling the findMax method to find the largest node
        while (true){
            if (node.right != null) {
                node = node.right;
            }else {
                break;
            }
        }
        return node;
    }
}

Tags: Algorithm data structure Binary tree

Posted on Sun, 05 Dec 2021 07:58:54 -0500 by phpchamps