Java binary search tree (BST)

Table of contents

1. Binary Search Tree (BST)

1. What is a binary search tree

2. Judging a binary search tree

2. Binary search tree CRUD operation

1. Data structure of binary search tree

2. Add operation

3. Find operation

1. Find the maximum value

2. Find the minimum value

3. Find any value

4. Delete operation

1. Remove the maximum value

2. Remove the minimum value

3. Delete any value

5. Other operations

1. Print operation (implementation of toString)

6. Overall code implementation

3. Related topics of binary search tree

1. Binary search tree and doubly linked list

1. Description of the topic

describe

Enter a description:

Return value description:

2. Problem analysis

3. Code implementation

2. Convert the ascending array into a balanced binary search tree

1. Description of the topic

2. Problem analysis

3. Code implementation


1. Binary Search Tree (BST)

1. What is a binary search tree

Binary Search Tree ( BST for short ) is a common binary tree data structure, which satisfies the following properties:

  1. For any node , all nodes in its left subtree are smaller than its value , and all nodes in its right subtree are greater than its value;
  2. For any node, its left subtree and right subtree are binary search trees.

Because the binary search tree has the above-mentioned properties, operations such as search, insertion, and deletion can be performed quickly . In a binary search tree, the time complexity to find an element is O(log n) , where n is the number of nodes in the tree. At the same time, in a binary search tree, all nodes in the tree can be output in a certain order (such as in-order traversal ), so it can also be used as a method of sorting data .

2. Judging a binary search tree

How to judge whether a tree is a binary search tree? We can't judge that it is a binary search tree just by the fact that the current node is greater than the left child node and smaller than the right child, so some people don't know the concept of binary search tree clearly People’s misunderstanding, the real definition should be that the current node is greater than any node on its left subtree and smaller than any node on the right subtree. In fact, the idea of ​​using non-recursion is to use in-order traversal to judge, because two The in-order traversal of the fork search tree must be in order , so we can write the code

    //二叉搜索树的中序遍历为有序序列
    public boolean isValidBST(TreeNode root) {
        travel(root);
        for (int i = 0; i < list.size() - 1; ++i) {
            if (list.get(i) >= list.get(i + 1))
                return false;
        }
        return true;


    }

    ArrayList<Integer> list = new ArrayList<>();

    public void travel(TreeNode root) {
        if (root == null)
            return;
        travel(root.left);
        list.add(root.val);
        travel(root.right);

    }

The idea of ​​recursion is also easy to judge. When we enter the left subtree, the value of the previous root node is the maximum value. When we enter the right subtree, the value of the previous root node is the minimum value. We only need the left The value of the subtree is less than the value of the previous root node, and the value of the right subtree is greater than the value of the root node, which can be judged.

    public boolean isValidBST(TreeNode root) {
        return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE);
    }

    public boolean isValidBST(TreeNode node, long lower, long upper) {
        if (node == null) {
            return true;
        }
        if (node.val <= lower || node.val >= upper) {
            return false;
        }
        return isValidBST(node.left, lower, node.val) && isValidBST(node.right, node.val, upper);
    }

2. Binary search tree CRUD operation

1. Data structure of binary search tree

First of all, it has the same data structure as a normal binary tree, and there must be a private internal class TreeNode (you can read this article if you don’t know the internal class: https://blog.csdn.net/qq_64580912/article/details/129310721 ) , as a binary tree node , save the value of the node and the pointing of the left and right child nodes, and then the root node attribute should also be used inside the BST class to represent the root node of this binary search tree , and then the size attribute , representing The number of nodes in the binary search tree

public class BST {
    private class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;

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

        @Override
        public String toString() {
            return "TreeNode{" +
                    "val=" + val +
                    '}';
        }
    }

    private TreeNode root;
    private int size;


}

2. Add operation

Because of the nature of the binary search tree: the value of the current node is larger than any node in the left subtree and smaller than any node in the right subtree. When adding, we cannot destroy the binary search tree This property, and when inserting, we always make the newly added node a leaf node, so when the value of the added node is less than the current root node, recursively insert to the left subtree, when the added node When the value of the point is greater than the current root node, recursively insert to the right subtree. When the root node is empty, we add the node, return the added node, and then recursively return to connect the node.

    public void add(int val) {
        this.root = add(root, val);
    }

    //插入的结点都是和根结点进行比较,如果大于根结点,右子树的插入,如果小于根结点就是左子树的插入
    public TreeNode add(TreeNode root, int val) {
        //寻找到了插入的位置
        if (root == null) {
            TreeNode node = new TreeNode(val);
            size++;
            return node;
        } else if (root.val > val) {//此时左子树插入
            root.left = add(root.left, val);
            return root;

        }
        //此时左子树插入
        root.right = add(root.right, val);
        return root;
    }

3. Find operation

1. Find the maximum value

iterative implementation

Iterating to find the maximum value is easy because of the nature of the binary tree: the value of the current node is larger than any node in the left subtree and smaller than any node in the right subtree, so it is easy to know the largest node Point is the rightmost node of the tree,

    //查找最大值 ---迭代实现
    public int findMax() {
        if (root == null) {
            throw new NoSuchElementException("root is null");
        }
        TreeNode temp = root;
        while (temp.right != null) {
            temp = temp.right;
        }
        return temp.val;

    }

recursive implementation

Recursion is actually relatively easy to implement. We only need to continuously recurse to the right subtree, and return to the current root node when the right child node of the current root node is empty.

    //查找最大值 ---递归实现
    public TreeNode findMax2() {
        if (root == null) {
            throw new NoSuchElementException("root is null");
        }
        return findMax2(root);

    }

    public TreeNode findMax2(TreeNode root) {
        if (root.right == null) {
            return root;
        }
        return findMax2(root.right);

    }

2. Find the minimum value

iterative implementation

Iterating to find the minimum value is easy because of the nature of the binary tree: the value of the current node is larger than any node in the left subtree and smaller than any node in the right subtree, so it is easy to know the smallest node Point is the leftmost node of the tree,

    //查找最小值 ---迭代实现
    public int findMin() {
        if (root == null) {
            throw new NoSuchElementException("root is null");
        }
        TreeNode temp = root;
        while (temp.left != null) {
            temp = temp.left;
        }
        return temp.val;

    }

recursive implementation

Recursion is actually relatively easy to implement. We only need to recurse continuously to the left subtree, and return to the current root node when the left child node of the current root node is empty.

    //查找最小值 ---迭代实现
    public TreeNode findMin2() {
        if (root == null) {
            throw new NoSuchElementException("root is null");
        }
        return findMin2(root);

    }

    public TreeNode findMin2(TreeNode root) {
        if (root.left == null) {
            return root;
        }
        return findMin2(root.left);

    }

3. Find any value

When an ordinary binary tree searches for any value, it is necessary to traverse the entire binary tree to see if it exists, while a binary search tree only needs to traverse half of the nodes, because it is still the nature of the binary tree: the value of the current node is greater than any value of the left subtree. The nodes are all larger and smaller than any node in the right subtree. When the node of the value to be found is smaller than the current root node, recursively traverse the left subtree. When the node of the value to be found is smaller than the current root node When the current root node is large, recursively traverse the right subtree, and return true directly when it is equal to the node of the value to be found; or return false directly when the root node is null.

    //是否包含值为val的结点
    public boolean contains(int val) {
        return contains(root, val);
    }

    private boolean contains(TreeNode root, int val) {
        if (root == null) {
            return false;
        }
        if (root.val == val) {
            return true;
        } else if (root.val > val) {
            return contains(root.left, val);
        }
        return contains(root.right, val);

    }

4. Delete operation

1. Remove the maximum value

In a binary search tree as shown in the figure below, if we want to delete the maximum value (that is, 7 nodes), we first need to find the position of the maximum node, which is the same as the method for finding the maximum value, directly recursing to the position of the maximum value , at this time the left subtree of the maximum value may still have nodes, what we need to do is to return the left node of the maximum value node (that is, the current root node), and then recursively return with the previous root The right subtree of the node is connected.

    public int removeMax() {
        int max = findMax();
        root = removeMax(root);
        size--;
        return max;
    }

    //删除为root的结点
    private TreeNode removeMax(TreeNode root) {
        if (root.right == null) {
            //当前结点为最大值结点
            TreeNode left = root.left;
            root.left = root = null;
            size--;
            return left;
        }
        root.right = removeMax(root.right);
        return root;


    }

2. Remove the minimum value

The same as the method of deleting the maximum value, it is nothing more than traversing to the left subtree, after traversing to the smallest node, returning the right subtree of the smallest node, and then connecting at one time.

    public int removeMin() {
        int min = findMin();
        root = removeMin(root);
        size--;
        return min;
    }

    //删除为root的结点
    private TreeNode removeMin(TreeNode root) {
        if (root.left == null) {
            //当前结点为最大值结点
            TreeNode right = root.right;
            root.right = root = null;
            size--;
            return right;
        }
        root.left = removeMin(root.left);
        return root;


    }

3. Delete any value

The operation of deleting any value is very complicated. Of course, there is also the same place as the previous search for any value, that is, when the deleted value is less than the current root node, traverse the left subtree, and when the deleted value is greater than the current root node At this time, traverse to the right subtree, of course, there may be cases where the node is empty (at this time, the node whose value is deleted does not exist in the binary search tree, just return null, which is equivalent to no perform any operation).

The next thing we need to consider is the operation when finding the node to be deleted, mainly divided into the following situations:

  • Case 1: The left and right subtrees of the current node (that is, the node to be deleted) are empty
  • Case 2: The left subtree of the current node is empty, and the right subtree is not empty
  • Case 3: The right subtree of the current node is empty, and the left subtree is not empty
  • Situation 4: Neither the left subtree nor the right subtree of the current node is empty

For case one, it can be solved very well, just return null directly. For case two, the left subtree is empty, we only need to return the right subtree, and delete the smallest node. For case three , the right subtree is empty, we only need to return the left subtree, which is the same idea as deleting the largest node. In fact, we can attribute case 1 to case 2 or case 3, because case 2 and case 3 return the right subtree If the tree or left subtree directly returns null for case 1, it is the type of case 2 or case 3.

For case 4, it is more complicated. In fact, we can change our thinking, we do not delete this node, we find a node to replace the position of the node to be deleted (successor), and we only need to return the replacement node (successor) That is, then which node should be found to replace the node to be deleted, and the structure of the binary search tree must not be destroyed. This node must be greater than any value of the left subtree and less than any value of the right subtree , In fact, it is to find the maximum value of the left subtree or the minimum value of the right subtree to replace, (take the maximum value of the left subtree as an example) We can find the left child of the node to be deleted by using the method we wrote The maximum value of the tree (findMax(root)), and then we connect the right child of the successor to (the tree removeMax(root) for deleting the maximum value of the left subtree), and point the left child node of the successor to the root node left subtree,

Note: These two steps cannot be reversed, so if the left child of successor points to root.left, then calling removeMax(root) may delete the data connection of the wrong node.

And don't size-- at this time, because removeMax(root) already contains size--;

    //在bst树中删除任意节点

    /**
     * @param val
     * @return 删除成功返回true, 失败返回false
     */
    public void remove(int val) {
        root = remove(root, val);
    }

    /**
     * 删除当前树中值为val的结点,没有返回null
     *
     * @param root
     * @param val
     * @return
     */
    private TreeNode remove(TreeNode root, int val) {
        //此时可能树中并不存在val=val的结点
        if (root == null) {
            return null;
        }
        //在左子树中进行删除
        if (root.val > val) {
            root.left = remove(root.left, val);
            return root;
        } else if (root.val < val) {//在右子树中进行删除
            root.right = remove(root.right, val);
            return root;
        } else {//当前结点就是要删除的结点
            if (root.left == null) {
                size--;
                TreeNode right = root.right;
                root.right = root = null;
                return right;
            }
            if (root.right == null) {
                size--;
                TreeNode left = root.left;
                root.left = root = null;
                return left;
            }
/*            //此时都不为空
            //寻找左子树的最大值或者右子树的最小值(采用这一种)来替代
            TreeNode successor = findMin2(root.right);
            //这一步的操作为把successor删除,并且将successor.right与root.right相连接,这两步的顺序不能改变
            successor.right = removeMin(root.right);
            //不需要size--,因为removeMin中已经进行了这个操作
            successor.left = root.left;
            root.left = root.right = root = null;
            return successor;*/

            //左子树的最大值替代
            TreeNode successor = findMax2(root.left);
            successor.right = removeMax(root.left);
            successor.left = root.left;
            return successor;


        }

    }

5. Other operations

1. Print operation (implementation of toString)

Print directly, and print layers.

    @Override
    //前序toString打印
    public String toString() {
        StringBuilder sb = new StringBuilder();
        gengerateBSTString(root, 0, sb);
        return sb.toString();

    }

    /**
     * 在当前以root为结点的树中,将当前结点的层次和值,拼接到sb对象中
     *
     * @param root
     * @param depth
     * @param sb
     */
    private void gengerateBSTString(TreeNode root, int depth, StringBuilder sb) {
        if (root == null) {
            sb.append(generateDepthString(depth) + "null\n");
            return;
        }
        sb.append(generateDepthString(depth).append(root.val + "\n"));
        gengerateBSTString(root.left, depth + 1, sb);
        gengerateBSTString(root.right, depth + 1, sb);
    }

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

6. Overall code implementation

public class BST {
    private class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;

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

        @Override
        public String toString() {
            return "TreeNode{" +
                    "val=" + val +
                    '}';
        }
    }

    private TreeNode root;
    private int size;

    @Override
    //前序toString打印
    public String toString() {
        StringBuilder sb = new StringBuilder();
        gengerateBSTString(root, 0, sb);
        return sb.toString();

    }

    /**
     * 在当前以root为结点的树中,将当前结点的层次和值,拼接到sb对象中
     *
     * @param root
     * @param depth
     * @param sb
     */
    private void gengerateBSTString(TreeNode root, int depth, StringBuilder sb) {
        if (root == null) {
            sb.append(generateDepthString(depth) + "null\n");
            return;
        }
        sb.append(generateDepthString(depth).append(root.val + "\n"));
        gengerateBSTString(root.left, depth + 1, sb);
        gengerateBSTString(root.right, depth + 1, sb);
    }

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

    public int removeMax() {
        int max = findMax();
        root = removeMax(root);
        size--;
        return max;
    }

    //删除为root的结点
    private TreeNode removeMax(TreeNode root) {
        if (root.right == null) {
            //当前结点为最大值结点
            TreeNode left = root.left;
            root.left = root = null;
            size--;
            return left;
        }
        root.right = removeMax(root.right);
        return root;


    }

    public int removeMin() {
        int min = findMin();
        root = removeMin(root);
        size--;
        return min;
    }

    //删除为root的结点
    private TreeNode removeMin(TreeNode root) {
        if (root.left == null) {
            //当前结点为最大值结点
            TreeNode right = root.right;
            root.right = root = null;
            size--;
            return right;
        }
        root.left = removeMin(root.left);
        return root;


    }
    //在bst树中删除任意节点

    /**
     * @param val
     * @return 删除成功返回true, 失败返回false
     */
    public void remove(int val) {
        root = remove(root, val);
    }

    /**
     * 删除当前树中值为val的结点,没有返回null
     *
     * @param root
     * @param val
     * @return
     */
    private TreeNode remove(TreeNode root, int val) {
        //此时可能树中并不存在val=val的结点
        if (root == null) {
            return null;
        }
        //在左子树中进行删除
        if (root.val > val) {
            root.left = remove(root.left, val);
            return root;
        } else if (root.val < val) {//在右子树中进行删除
            root.right = remove(root.right, val);
            return root;
        } else {//当前结点就是要删除的结点
            if (root.left == null) {
                size--;
                TreeNode right = root.right;
                root.right = root = null;
                return right;
            }
            if (root.right == null) {
                size--;
                TreeNode left = root.left;
                root.left = root = null;
                return left;
            }
/*            //此时都不为空
            //寻找左子树的最大值或者右子树的最小值(采用这一种)来替代
            TreeNode successor = findMin2(root.right);
            //这一步的操作为把successor删除,并且将successor.right与root.right相连接,这两步的顺序不能改变
            successor.right = removeMin(root.right);
            //不需要size--,因为removeMin中已经进行了这个操作
            successor.left = root.left;
            root.left = root.right = root = null;
            return successor;*/

            //左子树的最大值替代
            TreeNode successor = findMax2(root.left);
            successor.right = removeMax(root.left);
            successor.left = root.left;
            return successor;


        }

    }

    public int getSize() {
        return size;
    }

    public void add(int val) {
        this.root = add(root, val);
    }

    //插入的结点都是和根结点进行比较,如果大于根结点,右子树的插入,如果小于根结点就是左子树的插入
    public TreeNode add(TreeNode root, int val) {
        //寻找到了插入的位置
        if (root == null) {
            TreeNode node = new TreeNode(val);
            size++;
            return node;
        } else if (root.val > val) {//此时左子树插入
            root.left = add(root.left, val);
            return root;

        }
        //此时左子树插入
        root.right = add(root.right, val);
        return root;
    }

    //查找最大值 ---迭代实现
    public int findMax() {
        if (root == null) {
            throw new NoSuchElementException("root is null");
        }
        TreeNode temp = root;
        while (temp.right != null) {
            temp = temp.right;
        }
        return temp.val;

    }

    //查找最大值 ---递归实现
    public TreeNode findMax2() {
        if (root == null) {
            throw new NoSuchElementException("root is null");
        }
        return findMax2(root);

    }

    public TreeNode findMax2(TreeNode root) {
        if (root.right == null) {
            return root;
        }
        return findMax2(root.right);

    }

    //查找最小值 ---迭代实现
    public int findMin() {
        if (root == null) {
            throw new NoSuchElementException("root is null");
        }
        TreeNode temp = root;
        while (temp.left != null) {
            temp = temp.left;
        }
        return temp.val;

    }

    //查找最小值 ---迭代实现
    public TreeNode findMin2() {
        if (root == null) {
            throw new NoSuchElementException("root is null");
        }
        return findMin2(root);

    }

    public TreeNode findMin2(TreeNode root) {
        if (root.left == null) {
            return root;
        }
        return findMin2(root.left);

    }

    //是否包含值为val的结点
    public boolean contains(int val) {
        return contains(root, val);
    }

    private boolean contains(TreeNode root, int val) {
        if (root == null) {
            return false;
        }
        if (root.val == val) {
            return true;
        } else if (root.val > val) {
            return contains(root.left, val);
        }
        return contains(root.right, val);

    }

}

3. Related topics of binary search tree

1. Binary search tree and doubly linked list

1. Description of the topic

describe

Input a binary search tree and convert the binary search tree into a sorted doubly linked list. As shown below


Requirements: space complexity O(1) (that is, operate on the original tree), time complexity O(n)

Notice:

1. It is required that no new nodes can be created, only the pointing of the node pointers in the tree can be adjusted. After the conversion is completed, the left pointer of the node in the tree needs to point to the predecessor, and the right pointer of the node in the tree needs to point to the successor 2. Return the pointer of the
first node in the linked list
3. The TreeNode returned by the function has left and right pointers, which can be seen in fact Form a data structure of a doubly linked list 4. You don’t need to output the doubly linked list, the program will automatically print out according to your return value

Enter a description:

root node of binary tree

Return value description:

One of the head nodes of the doubly linked list.

Niuke: Binary Search Tree and Doubly Linked List_Niuke Topic_Niuke.com

2. Problem analysis

For problems like this type of binary tree, we generally use a recursive approach.

From general to special, for such a binary search tree, how do we turn it into a doubly linked list? At this time, root.left=left, left.right=root, root.right=right, right.left= root; this can be converted into a binary search tree

In fact, let's analyze the information of this recursive function again. For the return value: the returned head node of the doubly linked list, the parameter pRootOfTree is the root node of the current subtree. At this time, we only need to connect the root node to the left half linked list , to connect with the right half linked list, this problem can be solved, but we have to be careful about the problem of null pointers, it may be the case that the left half linked list or the right half linked list is empty, and the left half linked list returns the head node, we To find its tail node and connect it with the root node, the right half linked list directly connects the head node with the root node, and the final return value returns the head node, but when the head node is empty, it needs to return The current root node (the left half linked list is null at this time).

3. Code implementation

public class Solution {
    public TreeNode Convert(TreeNode pRootOfTree) {
        if (pRootOfTree == null) {
            return null;
        }
        TreeNode head = Convert(pRootOfTree.left);
        TreeNode tail = head;
        while (tail != null) {
            if (tail.right == null) {
                break;
            }
            tail = tail.right;
        }
        // 此时tail节点走到了左半链表的尾部
        // 2.将左半链表和根节点拼接
        if (tail != null) {
            // 左子树存在
            tail.right = pRootOfTree;
            pRootOfTree.left = tail;
        }
        // 3.转换右子树和根节点拼接
        TreeNode right = Convert(pRootOfTree.right);
        // 拼接根节点和右半链表
        if (right != null) {
            right.left = pRootOfTree;
            pRootOfTree.right = right;
        }
        //如果head==null,说明此时左边链表为空,直接返回pRootOfTree
        return head == null ? pRootOfTree : head;

    }
}

2. Convert the ascending array into a balanced binary search tree

1. Description of the topic

Given an array of integers numsin which the elements are sorted in ascending , please convert it into a height- balanced binary search tree.

A height-balanced binary tree is a binary tree that satisfies "the absolute value of the height difference between the left and right subtrees of each node does not exceed 1".

For example, when the input ascending array is [-1,0,1,2], the converted balanced binary search tree (BST) can be {1,0,2,-1}, as shown in the following figure:

Or {0,-1,1,#,#,#,2}, as shown below: 

2. Problem analysis

It needs to be converted into a balanced binary search tree, that is, the height difference between the left and right subtrees does not exceed 1. That is, every time we want to select the element in the middle of the array as the root node, we must ensure that the height difference does not exceed 1. At this point we need to create a new method, the parameters of the method should contain an array, the left pointer left, the right pointer right, every time mid is obtained, and it is used as the root node, then the left subtree recurses, and the right subtree recurses until left> When right returns null.

3. Code implementation

public class Solution {
    /**
     *
     * @param num int整型一维数组
     * @return TreeNode类
     */
    public TreeNode sortedArrayToBST (int[] num) {
        // write code here
        return convert(num, 0, num.length - 1);

    }
    public TreeNode convert(int[] num, int left, int right) {
        if (left > right) {
            return null;
        }
        int mid = left + ((right - left) >> 1);
        TreeNode node = new TreeNode(num[mid]);
        node.left = convert(num, left, mid - 1);
        node.right = convert(num, mid + 1, right);
        return node;


    }
}

Guess you like

Origin blog.csdn.net/qq_64580912/article/details/130049982