Red black tree? AVL? First understand the most basic binary tree!

Keywords: Java less

What is a tree?

Tree data structure is very common in life, such as going to the library to find a book, which is placed according to different categories. For example, the disk folder in the computer and so on. Using a tree structure to store data can be surprisingly efficient. As the name implies, the data structure of tree is like an inverted tree, which also has roots, branches and leaves.

Binomial explanation

  • Root node: the top node is called root node. The root node has no parent node, and a binary tree has only one root node
  • Child nodes: the nodes below each parent node are called child nodes, which are divided into left and right nodes. There can be one or two nodes, and there can be only two nodes at most
  • Leaf node: if a node has no children, it is called leaf node
  • Left and right subtrees: a binary tree has a natural recursive structural value. Each child node under the root node can also form its own binary tree.

Binary search tree

  • Binary search tree is a binary tree
  • The value of each node in the binary search tree is greater than that of all nodes in the left subtree and less than that of all nodes in the right subtree
  • The stored elements must be comparable, not necessarily digital, but also implement the comparison details of the compatible interface

The following are all binary trees. First of all, a binary tree is not a balanced binary tree. It doesn't matter if there are more nodes and fewer nodes, or if the depth is different.



Insert operation of element

First, use 28 to 41 to compare the size, which is smaller than 41. According to the characteristics of the binary tree, the nodes smaller than the current node will be on the left. Therefore, 28 to left, 28 to 22, 28 to 22, and 28 to 22. The large ones will go to the right and 33 to 33, 28 to 33, and the small ones will go to the left. When you see that the left side of 33 is empty, there is a place to put it in. Finally, 28 will be stored in the position of the left child node of 33 node.

public class BinarySearchTree<E extends Comparable>{
    private class Node {
        public E e;
        public Node left,right;
        public Node(E e) {
            this.e = e;
            this.left = null;
            this.right = null;
        }
    }

    private Node root; //Root node
    private int size;
    
    public boolean isEmpty() {
        return size == 0;
    }

    public int getSize() {
        return size;
    }

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

    public void add(E e) {
        //If there is no root node at this time
        if(root == null) {
            //Add element to root node
            root = new Node(e);
            size++;
        }
        else {
            add(root, e);//Represents a root node
        }
    }

    private void add(Node root,E e)
    {
        //1. The most fundamental problem. Compared with the current node, if the child node is empty, put it there
        if(e.equals(root.e)) {
            return;
        }

        else if(e.compareTo(root.e) < 0 && root.left == null) {
            root.left = new Node(e);
            size++;
            return;
        }
        else if(e.compareTo(root.e) > 0 && root.right == null) {
            root.right = new Node(e);
            size++;
            return;
        }
        //2. Turn the problem into a smaller problem process. This add method solves the problem of inserting elements into appropriate positions according to the current node comparison
        if(e.compareTo(root.e) < 0) {//Left side
            add(root.left,e);
        }
        else{ //Equality has been judged at the beginning. It only needs to write else here
            add(root.right,e);
        }
    }
}

Whether to include an element

private boolean contains(Node node,E e)
{
    if(node == null)
        return false;
    if(e.compareTo(node.e) == 0) { //Corresponding node found
        return true;
    }
    else if (e.compareTo(node.e) > 0) {  //If it is larger than the current node, continue to recurse to the right, otherwise continue to search to the left recursively
        return contains(node.right,e);
    }
    else{
        return contains(node.left,e);
    }
}

Traversal of binary search tree

Each element has three traversal opportunities, respectively, before calling the recursive functions on both sides, in the middle of the recursive functions on both sides, and after the recursive functions on both sides.

Preorder traversal
public void preOrder()
{
    preOrder(root);
}

private void preOrder(Node node) {
    if(node == null)
        return;
    System.out.print(node.e + "---");
    preOrder(node.left);
    preOrder(node.right);
}

  
//Sequential traversal
public void inOrder() {
    inOrder(root);
}

private void inOrder(Node node) {
    if(node == null)
        return;
    inOrder(node.left);
    System.out.print(node.e + "---");
    inOrder(node.right);
}

//Post order traversal
public void postOrder() {
    postOrder(root);
}

private void postOrder(Node node) {
    if(node == null)
        return;
    postOrder(node.left);
    postOrder(node.right);
    System.out.print(node.e + "---");
}

After these elements are added as follows, the structure of the tree should be as follows

int[] arr = {12,23,45,6,8,3,21};
for(int x = 0; x < arr.length; x++)
{
    tree.add(arr\[x\]);
}
tree.preOrder();

//--------------------------------

     12
   /    \
  6      23
 /  \    /  \
3    8  21   45

//Preorder traversal: 12 - 6 - 3 - 8 - 23 - 21 - 45
//Middle order traversal: 3 - 6 - 8 - 12 - 21 - 23 - 45
//Subsequent traversal: 3 - 8 - 6 - 21 - 45 - 23 - 12

Find min and Max

Because of the characteristics of binary search tree, the minimum value must be on the left. If the left child node of a node is null, then this node is the minimum value. Instead, the maximum is on the right.

//Maximum and minimum values found
public E getMin()
{
    Node min = getMin(root);
    return min.e;
}

private Node getMin(Node node)
{

    if(node.left == null)
        return node;
    return getMin(node.left);
}

//Maximum and minimum values found
public E getMax()
{
    Node min = getMax(root);
    return min.e;
}

private Node getMax(Node node)
{
    if(node.right == null)
        return node;
    return getMax(node.right);
}

Delete Max and min

It is similar to the idea of finding the maximum value and the minimum value, but we should pay attention to the following situation. The left child of 16 is empty, so the minimum value is 16. After 16 is deleted, the right child of 16 should be associated with 28. That is to say, when recursion, the left child node of the current node should be associated with the node returned after deleting the maximum value.

//Delete minimum element
public E removeMin()
{
    E min = getMin();
    root = removeMin(root);
    return min;
}

private Node removeMin(Node node)
{
    if(node.left == null) { //Here, if the right child node of a node is empty, the logic also holds.
        Node rightNode = node.right;
        node.right = null;
        size--;
        return rightNode;
    }
    node.left = removeMin(node.left);
    return node;
}

//Delete the largest element
public E removeMax()
{
    E max = getMax();
    root = removeMax(root);
    return max;
}

private Node removeMax(Node node)
{

    if(node.right == null) { //Here, if the right child node of a node is empty, the logic also holds
        Node leftNode = node.left;
        node.left = null;
        size--;
        return leftNode;
    }
    node.right = removeMax(node.right);
    return node;

}

Delete any node

For example, to delete 58, if 58 has no left child or one of the right child nodes, the logic is the same as that of deleting the maximum and minimum values. If there is no 50 left child node, then after 58 is deleted, the right node 60 will be returned. But if there are nodes on both sides of 58, after 58 is deleted, there will be a problem of who will be the "boss". There are two people who can be the boss, one is 59, the other is 53. That is, the minimum value of the right subtree or the maximum value of the left subtree.

After knowing this node, you can maintain the relationship of this node, and finally return this node to the upper layer. In fact, maintaining the relationship is to make the left child node of 59 become the current left child node of 58, and the right child node of 59 become a new node other than 59. Finally, we can kill the relationship of 58.

public void deleteNode(E e)
{
    root = deleteNode(root,e);
}

private Node deleteNode(Node node, E e) {
    if(node == null)
        return null;
    if(e.compareTo(node.e) > 0) {
        node.right = deleteNode(node.right,e);
        return node;
    }
    else if(e.compareTo(node.e) < 0) {
        node.left = deleteNode(node.left,e);
        return node;
    }
    else{ //The nodes are equal
        //Left subtree is empty
        if(node.left == null) {
            Node rightNode = node.right;
            node.right = null;
            size--;
            return rightNode;
        }
        //Left subtree is empty
        if(node.right == null) {
            Node leftNode = node.left;
            node.left = null;
            size--;
            return leftNode;
        }

        //1. Find the successor of the node to be deleted. The successor is the smallest one in the current right child node
        //2. Replace the node to be deleted with the successor
        Node successor = getMin(node.right);
        successor.right = removeMin(node.right);
        successor.left = node.left;
        node.left = node.right = null;
        return successor;
    }
}

Posted by vic vance on Thu, 14 Nov 2019 02:23:45 -0800