Notes on learning data structure (12) -- [balanced binary search tree (AVLTREE)]

Station B learning portal – > Shang Silicon Valley Java data structure and Java algorithm (Java data structure and algorithm)

Case scenario introduction

For example, given a set of numbers 1,2,3,4,5,6, it should be arranged into a binary search tree; Is it necessary to become such a tree below;

If you look carefully, it is more like a one-way linked list; Adding and deleting is fast, but querying is slow; Because the tree needs to be retrieved when querying, the efficiency will be even slower;

Self balancing binary search tree; The height difference between the left and right subtrees shall not exceed 1;
The common implementation of balanced binary tree includes AVL tree, red black tree and extended tree

AVL tree is the first self balanced binary search tree (balanced binary search tree). In AVL tree, the maximum height difference between two subtrees of any node is 1, so it is also called height balanced tree. Adding and deleting may require rebalancing the tree by one or more tree rotations;
The premise is that this tree is a binary search tree

Make a few case diagrams to see;

Left rotation diagram

For example, there is a sequence 4,3,6,5,7,8;
The binary search tree is formed as follows, but it does not comply with the balanced tree rules

Then you need to rotate left

First, copy the root node of the current tree to create a new node; four

Then, the left child node of the current node 4 is taken as the left child node of the new node 4;

The left child node 5 of the right child node 6 of the current node 4 is the right child node of the new node 4

Then let the right child node 6 of the current node 4 replace the current node 4

Let the right child node 7 of the right child node 6 of the current node 6 be the right child node of the current node 6

Let the new node 4 be the left child node of the current node 6;
Balanced binary search tree construction completed

Implemented by code

Note that only the case where the right subtree is higher than the left subtree is considered when this left rotation is performed; Right rotation and double rotation will be considered later;
At the bottom of the node, the binary search tree in the previous section should be used as the basis;
—>Implementation of learning binary search tree for learning data structure [12]
Define the method to calculate the height of the tree, the height of the left subtree and the height of the right subtree under the internal class TreeNode

 /**
     * Calculate the height of the left subtree;
     * @return Height of left subtree
     */
    public int getLeftHeight() {
        if (leftNode == null) {
            return 0;
        } else {
            return leftNode.getHeight();
        }
    }

    /**
     * Calculate the height of the right subtree
     * @return Height of right subtree
     */
    public int getRightHeight() {
        if (rightNode == null) {
            return 0;
        } else {
            return rightNode.getHeight();
        }
    }

    /**
     * Calculate the height of the current node (the underlying implementation in the node)
     * @return   Starting from the current node, the height of the tree
     */
    public int getHeight() {
        //Note that the number of layers of the current node is 1;
        return Math.max( leftNode != null ? leftNode.getHeight() : 0,
                        rightNode != null ? rightNode.getHeight() : 0) + 1;
    }

Before using the left rotation method is not defined; Test the currently constructed binary search tree

public class AVLTreeTest {
    public static void main(String[] args) {
        //Testing;
        int[] array = {4,3,6,5,7,8};
        AVLTree avlTree = new AVLTree();
        //Add the data in the array to the balanced binary tree;
        for (int j : array) {
            avlTree.addNode(new TreeNode(j));
        }
        //Traverse the tree in middle order;
        System.out.println("Traverse the binary search tree in middle order--");
        avlTree.infixList();
        System.out.println("------------------------");
        System.out.println("Before left rotation--");
        System.out.println("Tree height"+avlTree.getRoot().getHeight());
        System.out.println("The height of the left subtree of the current tree"+avlTree.getRoot().getLeftHeight());
        System.out.println("The height of the right subtree of the current tree"+avlTree.getRoot().getRightHeight());
    }
}

Test results;

Traverse the binary search tree in middle order--
TreeNode{val=3}
TreeNode{val=4}
TreeNode{val=5}
TreeNode{val=6}
TreeNode{val=7}
TreeNode{val=8}
------------------------
Before left rotation--
Tree height 4
 Left subtree height of current tree 1
 Right subtree height of current tree 3

After defining the left rotation method;
Similarly, the left rotation node method is defined under the internal class TreeNode, and the logical processing is carried out according to the idea of graphical analysis;

/**
 * Left rotation binary search tree;
 */
public void RotateLeft(){
    //1. First create a new node; The value is the current node
    TreeNode newNode = new TreeNode(val);
    //2. Then, the left child node of the current node will be set as the left child node of the new node;
    newNode.leftNode = this.leftNode;
    //3. The left child node of the right child node of the current node is set as the right child node of the new node;
    newNode.rightNode = this.rightNode.leftNode;
    //4. Replace and overwrite the current node with the right child node value of the current node;
    this.val = this.rightNode.val;
    //5. Note that the value of the current node has changed at this time; Here, let the right node of the current node be the right node of the current node; Set as the right node of the current node;
    this.rightNode = this.rightNode.rightNode;
    //6. Connect the new node to the left child node of the current node;
    this.leftNode = newNode;
}

It is called at the bottom of the method of adding nodes

/**
 * The underlying method of adding nodes;
 * @param node The node to be added;
 */
public void addNode(TreeNode node) {
    //First judge whether the current node is empty;
    if (node == null) return;

    if (node.val <= this.val) {
        //Consider hanging to the left child node; If the left node already exists, continue recursion to the left;
        if (this.leftNode == null) {
            this.leftNode = node;
        } else {
            this.leftNode.addNode(node);
        }
    } else {
        //Right child node; If a node already exists, continue recursion to the right;
        if (this.rightNode == null) {
            this.rightNode = node;
        } else {
            this.rightNode.addNode(node);
        }
    }
    //First, when the right subtree is higher than the left subtree, the left rotation is processed;
    if(getRightHeight() - getLeftHeight() >1){
        RotateLeft();
    }
}

Note that this is not the final processing. Only the case where the right subtree is higher than the left subtree is considered;

Test and use the same;

The difference between the left and right heights of the current tree conforms to the rules of balanced binary tree

Traverse the binary search tree in middle order--
TreeNode{val=3}
TreeNode{val=4}
TreeNode{val=5}
TreeNode{val=6}
TreeNode{val=7}
TreeNode{val=8}
------------------------
After left rotation processing--
Tree height 3
 Left subtree height of current tree 2
 The right subtree height of the current tree is 2

Right rotation diagram

First, a binary search tree formed by a group of numbers 10,8,12,7,9,6 is used as a case;
At this time, if you want him to become a balanced binary search tree, you have to change the situation;

First, copy and create a new node 10 according to the current root node 10

Then hook the right child node 12 of the current node 10 to the right child node of the new node 10

Then, the right child node 9 of the left child node 8 of the current node 10 is set as the left child node of the new node 10

Replace the value of the current node with the value of the left child node 8 of the current node 10

Then, the left child node 7 of the left child node 8 of the current node 8 is set as the left child node of the current node 8

Then, the new node 10 is set as the right child node of the current node 8

Specific implementation of right rotation

Before right rotation without processing;

Probably the structure is like this

Add the right rotation method to the internal class TreeNode, and carry out logical processing according to the idea of illustration;

/**
 * Right rotation binary search tree
 */
public void RotateRight(){
    //1. First create a new node with the value of the current node;
    TreeNode newNode = new TreeNode(val);
    //2. Take the right child node of the current node as the right child node of the new node;
    newNode.rightNode = this.rightNode;
    //3. Take the right child node of the left child node of the current node as the left child node of the new node;
    newNode.leftNode = this.leftNode.rightNode;
    //4. Let the value of the left child node of the current node replace the value of the current node;
    this.val = this.leftNode.val;
    //5. Connect the left child node of the left child node of the current node to the left child node position of the current node;
    this.leftNode = this.leftNode.leftNode;
    //6. Set the of the new node as the right child node of the current node;
    this.rightNode = newNode;
}

Add the call of the right rotation method to the add method

  /**
     * The underlying method of adding nodes;
     * @param node The node to be added;
     */
    public void addNode(TreeNode node) {
        //First judge whether the current node is empty;
        if (node == null) return;

        if (node.val <= this.val) {
            //Consider hanging to the left child node; If the left node already exists, continue recursion to the left;
            if (this.leftNode == null) {
                this.leftNode = node;
            } else {
                this.leftNode.addNode(node);
            }
        } else {
            //Right child node; If a node already exists, continue recursion to the right;
            if (this.rightNode == null) {
                this.rightNode = node;
            } else {
                this.rightNode.addNode(node);
            }
        }
        //Here, when the right subtree is higher than the left subtree, the left rotation is considered;
        if(getRightHeight() - getLeftHeight() > 1){
            RotateLeft();
        }
        //When adding nodes, when the left subtree is higher than the right subtree, the right rotation is performed;
        if(getLeftHeight() - getRightHeight() > 1){
            RotateRight();
        }
    }

Then conduct test treatment;
Note that the consideration here is only the special case where the left tree is higher than the right tree; Treatment;

Double rotation processing

For example, the following tree structure;

After right rotation processing; This still doesn't conform to the rules of balanced binary tree

It actually rotates right and becomes like this;

Then, double rotation must be considered here; Literally, left rotation and right rotation are combined according to the situation;

Analyze ideas;
First, look at the tree. If the right subtree of the left child of the current root node is higher than the left child tree, rotate the left node 7 of the current node to the left; Then, it is connected to the root node 10;
Then note the structure at this time. If the left subtree of the left child of the current root node is higher than the right subtree, rotate the current node to the right;

Double rotation concrete implementation

Here we have to consider four situations;

When it conforms to the left rotation (the right subtree of the current node is higher than the left subtree);

  • (1) The left subtree of the right child node of the current node is higher than the right subtree; Then you have to rotate the right child node of the current node first; Then rotate the current node to the left;
  • (2) If the right subtree of the right child node of the current node is higher than the left subtree; Directly rotate the current node to the left;

When the right rotation is met (the left subtree of the current node is higher than the right subtree);

  • (3) If the right subtree of the left child node of the current node is higher than the left subtree; First, rotate the left child node of the current node to the left; Then right rotate the current node;
  • (4) If the left subtree of the left child node of the current node is higher than the right subtree, directly rotate the current node to the right;

This time, you can modify it at the bottom adding method of the internal class TreeNode;

/**
 * The underlying method of adding nodes;
 * @param node The node to be added;
 */
public void addNode(TreeNode node) {
    //First judge whether the current node is empty;
    if (node == null) return;

    if (node.val <= this.val) {
        //Consider hanging to the left child node; If the left node already exists, continue recursion to the left;
        if (this.leftNode == null) {
            this.leftNode = node;
        } else {
            this.leftNode.addNode(node);
        }
    } else {
        //Right child node; If a node already exists, continue recursion to the right;
        if (this.rightNode == null) {
            this.rightNode = node;
        } else {
            this.rightNode.addNode(node);
        }
    }
    //When adding nodes, the right subtree is higher than the left subtree, and the left rotation is processed;
    if(getRightHeight() - getLeftHeight() > 1){
        //If the left subtree of the right child node of the current node is higher than the right subtree; First, right rotate the right child node of the current node;
        if(this.rightNode!=null && this.rightNode.getLeftHeight() > this.rightNode.getRightHeight()){
            this.rightNode.RotateRight();
            //Rotate the current node to the left;
            this.RotateLeft();
        }else{
            //Otherwise, rotate left directly;
            RotateLeft();
        }
        return;
    }
    //When adding nodes, when the left subtree is higher than the right subtree, the right rotation is performed;
    if(getLeftHeight() - getRightHeight() > 1){
        //If the right subtree of the left child node of the current node is higher than the left subtree; First, rotate the left child node of the current node to the left;
        if(this.leftNode!=null && this.leftNode.getRightHeight() > this.leftNode.getLeftHeight()){
            this.leftNode.RotateLeft();
            //Then right rotate the current node;
            this.RotateRight();
        }else{
            RotateRight();
        }
    }
}

Testing; Consistent with the analysis;

Balanced binary search tree code summary

Final summary;

/**
 * @author by CSDN@Xiaozhi RE0
 * @date 2021-11-20 11:22
 * balanced binary tree
 */
//Balanced binary tree;
class AVLTree{
    //Root node;
    private TreeNode root;
    //Get the current root node;
    public TreeNode getRoot() {
        return root;
    }

    /**
     * Add a new node in the binary search tree;
     * @param node New nodes to be added;
     */
    public void addNode(TreeNode node) {
        //If the root node is empty, the first newly added node will be used as the root node temporarily;
        if (root == null) root = node;

        else {
            root.addNode(node);
        }
    }

    /**
     * Traversing the binary search tree in middle order;
     */
    public void infixList() {
        if (root == null)
            throw new RuntimeException("the tree is empty");
        root.infixList();
    }
    /**
     * Find the node to be deleted according to the given value; (defined under binary tree)
     *
     * @param val Given value
     * @return Find the node to delete;
     */
    public TreeNode getTreeNode(int val) {
        if (root == null) {
            return null;
        } else {
            return root.getTreeNode(val);
        }
    }

    /**
     * You need to find the parent node of the deleted node; (defined under binary tree)
     *
     * @param val The node to be deleted;
     * @return The parent node of the node to be deleted;
     */
    public TreeNode getParentByDelete(int val) {
        if (root == null) {
            return null;
        } else {
            return root.getParentByDelete(val);
        }
    }

    /**
     * Delete node;
     *
     * @param val Specify the value to delete the node;
     */
    public void deleteNode(int val) {
        if (root == null) return;

        //1. Find the node to be deleted first;
        TreeNode wantDeleteNode = getTreeNode(val);
        if (wantDeleteNode == null) {
            //Node not found;
            throw new RuntimeException("The node to be deleted was not found");
        }
        if ((root.leftNode == null && root.rightNode == null)) {
            //If there is only root node, delete it
            root = null;
            return;
        }
        //2. Find the parent node of the deleted node;
        TreeNode parentNode = getParentByDelete(val);

        //1:  The deleted node is a leaf node;
        if (wantDeleteNode.leftNode == null && wantDeleteNode.rightNode == null) {
            //Then confirm whether the deleted node is the left node or the right node of its parent node;
            if (parentNode.leftNode != null && parentNode.leftNode.val == val) {
                //Directly empty the left node position of the parent node;
                parentNode.leftNode = null;
            } else if (parentNode.rightNode != null && parentNode.rightNode.val == val) {
                //Empty the right node position of the parent node;
                parentNode.rightNode = null;
            }
        }

        //2:  There are two child nodes under the deleted node;
        else if (wantDeleteNode.leftNode != null && wantDeleteNode.rightNode != null) {
            //Delete and find the smallest node under the right subtree;
            int rightTreeMinNode = getAndDelRightTreeMinNode(wantDeleteNode);
            //Let the minimum node under the right subtree replace the node value to be deleted;
            wantDeleteNode.val = rightTreeMinNode;
        }
        //3:  There is a child node under the deleted node;
        else {
            //The child node under the deleted node is the left child node;
            if (wantDeleteNode.leftNode != null) {
                //First, consider whether the parent node still exists;
                if (parentNode != null) {
                    //The deleted node is the left child node of the parent node;
                    if (parentNode.leftNode.val == val) {
                        parentNode.leftNode = wantDeleteNode.leftNode;
                    }
                    //The deleted node is the right child node of the parent node;
                    else {
                        parentNode.rightNode = wantDeleteNode.leftNode;
                    }
                } else {
                    root = wantDeleteNode.leftNode;
                }
            }
            //The child node under the deleted node is the right child node;
            else {
                //First, consider whether the parent node still exists;
                if (parentNode != null) {
                    //The deleted node is the left child node of the parent node;
                    if (parentNode.leftNode.val == val) {
                        parentNode.leftNode = wantDeleteNode.rightNode;
                    }
                    //The deleted node is the right child node of the parent node;
                    else {
                        parentNode.rightNode = wantDeleteNode.rightNode;
                    }
                } else {
                    root = wantDeleteNode.rightNode;
                }
            }
        }
    }

    /**
     * Find and delete the smallest node under the right subtree '
     *
     * @param node Current incoming node
     * @return The lowest node under the returned node tree;
     */
    public int getAndDelRightTreeMinNode(TreeNode node) {
        //It is actually operated with a temporary node;
        TreeNode tempNode = node;
        while (tempNode.leftNode != null) {
            tempNode = tempNode.leftNode;
        }
        //Delete this node;
        deleteNode(tempNode.val);
        //Return to the node;
        return tempNode.val;
    }
}

//node
class TreeNode {
    //Left node, right node; Node value;
    TreeNode leftNode;
    TreeNode rightNode;
    int val;

    //Initialize the node;
    public TreeNode(int val) {
        this.val = val;
    }

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


    /**
     * Calculate the height of the left subtree;
     * @return Height of left subtree
     */
    public int getLeftHeight() {
        if (leftNode == null) {
            return 0;
        } else {
            return leftNode.getHeight();
        }
    }

    /**
     * Calculate the height of the right subtree
     * @return Height of right subtree
     */
    public int getRightHeight() {
        if (rightNode == null) {
            return 0;
        } else {
            return rightNode.getHeight();
        }
    }

    /**
     * Calculate the height of the current node (the underlying implementation in the node)
     * @return   Starting from the current node, the height of the tree
     */
    public int getHeight() {
        //Note that the number of layers of the current node is 1;
        return Math.max( leftNode != null ? leftNode.getHeight() : 0,
                        rightNode != null ? rightNode.getHeight() : 0) + 1;
    }

    /**
     * Left rotation binary search tree;
     */
    public void RotateLeft(){
        //1. First create a new node; The value is the current node
        TreeNode newNode = new TreeNode(val);
        //2. Then, the left child node of the current node will be set as the left child node of the new node;
        newNode.leftNode = this.leftNode;
        //3. The left child node of the right child node of the current node is set as the right child node of the new node;
        newNode.rightNode = this.rightNode.leftNode;
        //4. Replace and overwrite the current node with the right child node value of the current node;
        this.val = this.rightNode.val;
        //5. Note that the value of the current node has changed at this time; Here, let the right node of the current node be the right node of the current node; Set as the right node of the current node;
        this.rightNode = this.rightNode.rightNode;
        //6. Connect the new node to the left child node of the current node;
        this.leftNode = newNode;
    }

    /**
     * Right rotation binary search tree
     */
    public void RotateRight(){
        //1. First create a new node with the value of the current node;
        TreeNode newNode = new TreeNode(val);
        //2. Take the right child node of the current node as the right child node of the new node;
        newNode.rightNode = this.rightNode;
        //3. Take the right child node of the left child node of the current node as the left child node of the new node;
        newNode.leftNode = this.leftNode.rightNode;
        //4. Let the value of the left child node of the current node replace the value of the current node;
        this.val = this.leftNode.val;
        //5. Connect the left child node of the left child node of the current node to the left child node position of the current node;
        this.leftNode = this.leftNode.leftNode;
        //6. Set the of the new node as the right child node of the current node;
        this.rightNode = newNode;
    }

    /**
     * The underlying method of adding nodes;
     * @param node The node to be added;
     */
    public void addNode(TreeNode node) {
        //First judge whether the current node is empty;
        if (node == null) return;

        if (node.val <= this.val) {
            //Consider hanging to the left child node; If the left node already exists, continue recursion to the left;
            if (this.leftNode == null) {
                this.leftNode = node;
            } else {
                this.leftNode.addNode(node);
            }
        } else {
            //Right child node; If a node already exists, continue recursion to the right;
            if (this.rightNode == null) {
                this.rightNode = node;
            } else {
                this.rightNode.addNode(node);
            }
        }
        //When adding nodes, the right subtree is higher than the left subtree, and the left rotation is processed;
        if(getRightHeight() - getLeftHeight() > 1){
            //If the left subtree of the right child node of the current node is higher than the right subtree; First, right rotate the right child node of the current node;
            if(this.rightNode!=null && this.rightNode.getLeftHeight() > this.rightNode.getRightHeight()){
                this.rightNode.RotateRight();
                //Rotate the current node to the left;
                this.RotateLeft();
            }else{
                //Otherwise, rotate left directly;
                RotateLeft();
            }
            return;
        }
        //When adding nodes, when the left subtree is higher than the right subtree, the right rotation is performed;
        if(getLeftHeight() - getRightHeight() > 1){
            //If the right subtree of the left child node of the current node is higher than the left subtree; First, rotate the left child node of the current node to the left;
            if(this.leftNode!=null && this.leftNode.getRightHeight() > this.leftNode.getLeftHeight()){
                this.leftNode.RotateLeft();
                //Then right rotate the current node;
                this.RotateRight();
            }else{
                RotateRight();
            }
        }
    }

    /**
     * Middle order traversal of the underlying implementation;
     */
    public void infixList() {
        //In the order of left, middle and right;
        if (this.leftNode != null) {
            this.leftNode.infixList();
        }
        System.out.println(this);
        if (this.rightNode != null) {
            this.rightNode.infixList();
        }
    }

    /**
     * Find the node to be deleted according to the given value; (implemented in the node)
     *
     * @param val Given value
     * @return Find the node to delete;
     */
    public TreeNode getTreeNode(int val) {
        if (val == this.val) {
            return this;
        } else if (val < this.val) {
            //Find in the left child node;
            if (this.leftNode != null) {
                return this.leftNode.getTreeNode(val);
            } else {
                //If the left child node is empty, it means that it cannot be found;
                return null;
            }
        } else {
            //Find in the right child node;
            if (this.rightNode != null) {
                return this.rightNode.getTreeNode(val);
            } else {
                //If the right child node is empty, it means that it cannot be found;
                return null;
            }
        }
    }

    /**
     * You need to find the parent node of the deleted node; (implemented under the node)
     *
     * @param val The node to be deleted;
     * @return The parent node of the node to be deleted;
     */
    public TreeNode getParentByDelete(int val) {
        //Ensure that it is not empty; That is, the parent node of the deleted node is found;
        if ((this.leftNode != null && this.leftNode.val == val) ||
                (this.rightNode != null && this.rightNode.val == val)) {
            return this;
        }
        //Find the left subtree;
        else if (val <= this.val && this.leftNode != null) {
            return this.leftNode.getParentByDelete(val);
        } else if (val > this.val && this.rightNode != null) {
            return this.rightNode.getParentByDelete(val);
        } else {
            //The parent node of the node to be deleted was not found; Such as root node;
            return null;
        }
    }
}

Tags: data structure

Posted on Wed, 24 Nov 2021 21:49:57 -0500 by Daniel Mott