Data Structure - Implementation of Binary Search Tree

definition

Binary Search Tree (BST), also known as Binary Sort Tree or Binary Search Tree.

Compared with ordinary binary trees, non-empty binary search trees have the following properties:

  1. All key values ​​of the non-empty left subtree are less than the key value of its root node ;
  2. All key values ​​of the non-empty right subtree are greater than the key value of its root node ;
  3. The left and right subtrees are both binary search trees ;
  4. There are no nodes in the tree with equal keys .

It can be seen that the nature of the binary search tree is very distinct, which also makes the binary tree have practical significance.

Common operations on binary search trees

For a binary search tree, in addition to the four conventional traversals, there are the following key operations worthy of our attention.

Binary Search Tree ADT

Implementation of storage structure of binary tree

For binary trees, we are still accustomed to choose to use a chain storage structure to implement.

Binary tree node definition

The biggest feature of a binary search tree is that its elements can be compared in size. This is something to be aware of.

/**
 * Created by engineer on 2017/10/26.
 * <p>
 * 二叉搜索树树结点定义
 */

public class TreeNode<T extends Comparable<T>> {
    
    

    // 数据域
    private T data;
    // 左子树
    public TreeNode<T> leftChild;
    // 右子树
    public TreeNode<T> rightChild;


    public TreeNode(T data) {
        this(null, data, null);
    }

    public TreeNode(TreeNode leftChild, T data, TreeNode rightChild) {
        this.leftChild = leftChild;
        this.data = data;
        this.rightChild = rightChild;
    }

    public T getData() {
        return data;
    }

    public TreeNode<T> getLeftChild() {
        return leftChild;
    }

    public TreeNode<T> getRightChild() {
        return rightChild;
    }

    public void setData(T data) {
        this.data = data;
    }

}

binary search tree insertion

With the root node, we can build a binary tree from the root node according to the properties of the binary tree.

/**
     * 树中插入元素
     *
     * @param value
     */
    void insert(T value) {
        if (value == null) {
            return;
        }
        root = insert(root, value);
    }

    private TreeNode<T> insert(TreeNode<T> node, T value) {
        if (node == null) {
            // 树为空,则创建根节点
            return new TreeNode<>(value);
        } else {
            if (compare(node, value) < 0) { // 插入值比根节点小,在左子树继续创建二叉搜索树
                node.leftChild = insert(node.getLeftChild(), value);
            } else if (compare(node, value) > 0) { // 插入值比根节点大,在右子树继续创建二叉搜索树
                node.rightChild = insert(node.getRightChild(), value);
            }
        }

        return node;
    }
    private int compare(TreeNode<T> node, T value) {
        return value.compareTo(node.getData());
    }

According to the characteristics of the binary search tree, we can easily use recursion to implement the insertion operation of the binary tree; in general, each time a node is inserted, the comparison occurs from the root node, and the smaller one is inserted into the left subtree, and the larger one Insert into the right subtree . This is exactly the same as the definition of a binary search tree.

We can simply test the correctness of this insert method.

Testing Binary Search Tree Insertion Operation

public class BinarySearchTreeTest {

    private static Integer[] arrays = new Integer[]{
   
   10, 8, 3, 12, 9, 4, 5, 7, 1,11, 17};

    public static void main(String[] args) {
        BinarySearchTree<Integer> mSearchTree = new BinarySearchTree<>();

        for (Integer data : arrays) {
            mSearchTree.insert(data);
        }
        // 打印二叉树的三种遍历顺序
        mSearchTree.printTree();

    }
}

The traversal of the tree has been analyzed in detail above , and will not be discussed in depth here.

A random array is defined here, which inserts the array into the tree in order, and prints the tree according to the three traversal structures of the tree. Following this array we will construct a binary search tree as shown below:

hand drawn binary tree

Take a look at the traversal results output by the program.

前序遍历:10 8 3 1 4 5 7 9 12 11 17 
中序遍历:1 3 4 5 7 8 9 10 11 12 17 
后序遍历:1 7 5 4 3 9 8 11 17 12 10 

It can be seen that the traversal result is consistent with the binary tree we drew, so it can be verified that the insertion method is correct.

find

Through the insertion operation, we have implemented a binary search tree, let's see how to find elements from the tree.

  • Find max and min

According to the characteristics of the binary search tree, we know that in a binary search tree, the minimum value must be on the leftmost node, and the maximum value must be on the rightmost node. Therefore, finding the maximum value of a binary tree becomes very easy.


/**
     * 查找最大值
     *
     * @return
     */
    public T findMax() {
        if (isEmpty()) return null;
        return findMax(root);
    }

    /**
     * 从特定结点开始寻找最大值
     *
     * @param node
     * @return
     */
    private T findMax(TreeNode<T> node) {
        TreeNode<T> temp = node;
        while (temp.getRightChild() != null) {
            temp = temp.getRightChild();
        }
        return temp.getData();
    }


    /**
     * 查找最小值
     *
     * @return
     */
    public T findMin() {
        if (isEmpty()) return null;
        return findMin(root);
    }

    /**
     * 从特定结点开始寻找最小值
     *
     * @param node
     * @return
     */
    private T findMin(TreeNode<T> node) {
        TreeNode<T> temp = node;
        while (temp.getLeftChild() != null) {
            temp = temp.getLeftChild();
        }
        return temp.getData();
    }

It can be seen that the implementation of the algorithm is very simple, that is, to continuously move the node backward to find the node without subtree, which is the node at the most boundary position.

  • Find a specific value

In a binary search tree, how to quickly find a node whose value is a specific element? Think about how we implement node insertion? This question is very simple.

Recursive implementation to find a specific node

**
/**
     * find 特定值 递归实现
     *
     * @param value
     * @return
     */
    public TreeNode<T> find(T value) {
        if (isEmpty()) {
            return null;
        } else {
            return find(root, value);
        }
    }

    private TreeNode<T> find(TreeNode<T> node, T value) {
        if (node == null) {
            // 当查找一个不在树中元素时,抛出异常
            throw  new RuntimeException("the value must not in the tree");
        }

        if (compare(node, value) < 0) {
            // 小于根节点时,从去左子树找
            return find(node.getLeftChild(), value);
        } else if (compare(node, value) > 0) {
            // 大于根节点时,从右子树找
            return find(node.getRightChild(), value);
        } else {
            // 刚好等于,找到了
            return node;

            // 剩下还有一种情况,就是不等于,也就是所查找的元素不在树中
        }
    }

The implementation idea of ​​search is generally the same as that of insertion; it is nothing more than doing different operations; it should be noted here that, for the robustness of the program, we also have to consider the situation if the searched element is not in the tree.

Iterate over the implementation, looking for a specific value

With the previous experience of finding the maximum and minimum values, we can also consider using an iterative algorithm to implement the algorithm for finding the specified element.


/**
     * 查找特定值-非递归实现
     *
     * @param value
     * @return 结点
     */
    public TreeNode<T> findIter(T value) {
        TreeNode<T> current = root;
        while (current != null) {
            if (compare(current, value) < 0) {
                current = current.getLeftChild();
            } else if (compare(current, value) > 0) {
                current = current.getRightChild();
            } else {
                return current;
            }
        }
        // current为null,说明所查找的元素不在tree里
        return null;
    }

Here is the same test to find the correctness of the method:

        System.out.printf("\nfind value %d in mSearchTree \n", 12);
        TreeNode mTreeNode = mSearchTree.find(12);
        TreeNode mTreeNode_1 = mSearchTree.findIter(12);
        System.out.println("递归实现结点  = :" + mTreeNode + ", value=" + mTreeNode.getData());
        System.out.println("非递归实现结点= :" + mTreeNode_1 + ", value=" + mTreeNode_1.getData());

        System.out.println("\nfind the max value in mSearchTree = " + mSearchTree.findMax());
        System.out.println("find the min value in mSearchTree = " + mSearchTree.findMin());

output:

find value 12 in mSearchTree 
递归实现结点  = :com.avaj.datastruct.tree.bst.TreeNode@4b67cf4d, value=12
非递归实现结点= :com.avaj.datastruct.tree.bst.TreeNode@4b67cf4d, value=12

find the max value in mSearchTree = 17
find the min value in mSearchTree = 1

We use recursion and iteration to find 12 respectively. You can see that the same object is found twice, and the value of this object is 12; the maximum and minimum values ​​found are also correct; therefore, the implementation of the search function is correct .

delete node

Deleting a node from a binary search tree can be regarded as the most complicated operation, mainly because after the node to be deleted is deleted, it still needs to keep the whole tree as a binary tree, so the needs are different. The situation is like analytics.

hand drawn binary tree

Take the binary tree we created above as an example, if the node to be deleted is a leaf node such as 1, 7, 11, 17, it is very easy; let its parent node point to null; and if It is 4,5 such a node that contains a subtree. In other words, this is actually a singly linked list. It is easier to delete a node from the middle of the singly linked list; the most troublesome is if the node to be deleted is Is 10,8,3,12 such nodes contain left and right subtrees, we need to find a maximum value from the left subtree, or the minimum value in the right subtree to replace this value. Summarize the operation of deleting nodes:

  • Leaf node: delete directly, its parent node points to null
  • A node containing a child: the parent node points to the self-node of the node to be deleted (equivalent to deleting an element in the middle of the linked list);
  • A node containing left and right subtrees: replace this node with the minimum value of the right subtree or the maximum value of the left subtree

Combined with the above analysis, the realization of deleting nodes from binary search tree is obtained.

/**
     * 从树中删除值为value 的特定结点
     *
     * @param value
     */
    public void delete(T value) {
        if (value == null || isEmpty()) {
            return;
        }

        root = delete(root, value);
    }


    private TreeNode<T> delete(TreeNode<T> node, T value) {

        // 结点为空,要出删除的元素不在树中
        if (node == null) {
            return node;
        }

        if (compare(node, value) < 0) { // 去左子树删除
            node.leftChild = delete(node.getLeftChild(), value);
        } else if (compare(node, value) > 0) { // 去右子树删除
            node.rightChild = delete(node.getRightChild(), value);
        } else { // 要删除的就是当前结点
            if (node.getLeftChild() != null && node.getRightChild() != null) {
   
   // 被删除的结点,包含左右子树
                T temp = findMin(node.getRightChild()); // 得到右子树的最小值
                node.setData(temp); //右子树最小值替换当前结点
                node.rightChild = delete(node.getRightChild(), temp); // 从右子树删除这个最小值的结点
            } else {
   
   // 被删除的结点,包含一个子树或没有子树
                if (node.getLeftChild() != null) {
                    node = node.getLeftChild();
                } else {
                    node = node.getRightChild();
                }
            }
        }

        return node;
    }

Here we choose to use the minimum value of the right subtree to replace, because it is easier to delete the node with this minimum value, because it must not be a node containing the left and right subtrees.

Similarly, here is a test of the function of deleting nodes:

        // 删除只带一个子树的结点
        mSearchTree.delete(4);
        mSearchTree.printTree();
        System.out.println();
        // 删除带左右子树的根节点
        mSearchTree.delete(10);
        mSearchTree.printTree();

output:

前序遍历:10 8 3 1 5 7 9 12 11 17 
中序遍历:1 3 5 7 8 9 10 11 12 17 
后序遍历:1 7 5 3 9 8 11 17 12 10 

前序遍历:11 8 3 1 5 7 9 12 17 
中序遍历:1 3 5 7 8 9 11 12 17 
后序遍历:1 7 5 3 9 8 17 12 11 

By comparing with the tree we drew at the beginning, it is found that it corresponds.

height of binary search tree

Finally, let's look at how to calculate the degree of a binary search tree.

public int getTreeHeight() {
        if (isEmpty()) {
            return 0;
        }

        return getTreeHeight(root);

    }

    private int getTreeHeight(TreeNode<T> node) {
        if (node == null) {
            return 0;
        }
        int leftHeight = getTreeHeight(node.getLeftChild());
        int rightHeight = getTreeHeight(node.getRightChild());
        int max = leftHeight > rightHeight ? leftHeight : rightHeight;
        // 得到左右子树中较大的返回.
        return max + 1;
    }

By the way, what is the height of the tree we created in the end after insertion and deletion operations.

System.out.println("\n\nTree's height =" + mSearchTree.getTreeHeight());

output:

Tree's height =5

It can be seen that since node 4 is deleted, the tree has changed from 6 layers to 5 layers, and the result is correct!


Well, that's all for the analysis of binary search trees! All source code addresses in the text .

Guess you like

Origin blog.csdn.net/TOYOTA11/article/details/78370678