Should handwritten AVL s be more efficient than red and black trees in STL s?✨


Introduction to AVL Tree

The name of the AVL tree comes from its inventors G.M. Adelson-Velsky and E.M. Landis. The AVL tree is the first self-balanced binary search tree (Self-Balancing Binary Search Tree, for short, Balanced Binary Tree).

An AVL tree has the following requirements:

  1. Condition one: It must be a binary lookup tree.
  2. Condition 2: The height difference between the left subtree and the right subtree of each node is at most 1.

The left child of node 45 of the left binary tree in figure 1, 46, is older than 45 and does not meet the criteria for a binary search tree, so it is not a balanced binary tree either.
The right binary tree satisfies the criteria for a binary search tree, while it satisfies criteria two, so it is a balanced binary tree.

Node 45 of left binary tree Left subtree height 2, Right subtree height 0, Left subtree height difference 2-0=2, does not satisfy condition 2. Nodes of right binary tree satisfy the difference of left subtree height up to 1, and it meets the requirements of binary search tree, so it is a balanced binary tree.

Finding, inserting, and deleting AVL trees are O (logn) on average and in worst case.Because each insertion requires constant adjustment and maintenance, in fact, too many insertions can also lead to a timeout deadline. The most advantageous operation is to find, because its underlying design makes it always strictly balanced no matter how many elements it inserts, so the AVL tree is suitableIf you need to find a balance between insertion and lookup operations, you can only choose red-black trees.

Concepts related to AVL trees

  1. Balance factor: The value that subtracts the height of the right subtree from the height of the left subtree of a node in a binary tree is called the Balance Factor for that node.
    In the AVL tree on the right of Figure 2:
    Node 50 has a left subtree height of 3, a right subtree height of 2, and BF= 3-2 = 1.
    Node 45 has a left subtree height of 2, a right subtree height of 1, and BF= 2-1 = 1.
    Node 46 has a left subtree height of 0, a right subtree height of 0, and BF= 0-0 = 0.
    Node 65 has a left subtree height of 0, a right subtree height of 1, and a BF of 0-1 = -1.
    For balanced binary trees, the BF value range is [-1,1]. If you find that the BF value of a node is not in this range, you need to adjust the tree.

  2. Minimum unbalance tree: A subtree whose absolute value is greater than 1 and closest to the inserting node is the root.

    In Figure 3, the BF of node 45 of the left binary tree is 1, and after inserting node 43, the BF of node 45 is 2. Node 45 is the node closest to insertion point 43 whose BF is not within the range of [-1,1], so the subtree rooted at node 45 is the smallest unbalanced subtree. (This corresponds to a recursive sequential return operation

  3. The precursor and successor of the middle order: As the name implies, the previous node and the latter node under the middle order traversal. Because of the time-binary search tree, the first node traversed in the middle order corresponds to the largest node smaller than this node, and the last node corresponds to the smallest node larger than this node. (This can be understood by the code below)This concept is useful when deleting nodes, since deleting a node requires that it be guaranteed to be still a binary search tree.
    For some tips on how to look for precursors and successors, check out another of my blogs: Interview Question 04.06. Successors

Detailed implementation of AVL tree

Overall mind mapping implementation.

1. Node structure

struct node {
    int val;
    int depth;
    node *lchild;
    node *rchild;

    node() : val(0), lchild(nullptr), rchild(nullptr) {}

    node(int x) : val(x), lchild(nullptr), rchild(nullptr) {}
};
  1. val, the value of the node.
  2. depth, the height of the node (the highest height in its left and right subtrees + 1).
  3. lchild, left child.
  4. rchild, right child.

2. Abstract Data Structure of AVL Tree (ADT)

class AVLTree {
    /*date part*/
    node *head;
    int length;
public:
    /*construct and destruct part*/
    AVLTree() : head(nullptr), length(0) {}

    AVLTree(int x) : head(new node(x)), length(1) {}

    ~AVLTree() {
        destroy(head);
    }

public:
    /*iterator part*/
    class iterator {//Encapsulation Iterator: Internal Class - only static functions of external classes can be called
        node *head;
        node *root;
    public:
        iterator(node *head, node *root) : head(head), root(root) {}

        iterator &operator++();

        bool operator==(const iterator &x);

        bool operator!=(const iterator &x);

        iterator operator++(int);

        iterator &operator--();

        iterator operator--(int);

        int operator*();
    };

private:
    /*static member function*/
    /*Rotate Part*/
    static node *rotateRight(node *root);

    static node *rotateLeft(node *root);

    static node *rotateLeftRight(node *root);

    static node *rotateRightLeft(node *root);

    /*Destruct*/
    static void destroy(node *root);

    /*Getter*/
    static node *getNext(node *root, node *p);

    static node *getPre(node *root, node *p);

    static node *getMinNode(node *root);

    static node *getMaxNode(node *root);

    static int get_depth(node *root);

    static void update_depth(node *root);

    /*Insert&Remove*/
    static node *Insert(int x, node *root, int &size);

    static node *remove(int x, node *root, int &size);

    /*print_order*/
    static void inorder(node *root);

public:
    /*public interface*/
    /*clear&empty*/
    void clear();

    bool isEmpty();

    /*find*/
    bool find(int x);

    /*insert&remove*/
    void insert(int x);

    void remove(int x);

    /*size*/
    int size();

    /*begin&end*/
    iterator begin();

    iterator end();

    /*print*/
    void inorder_print();
};

3. AVL tree height-dependent operations

Get Height

static int get_depth(node *root) {//Get Depth
    if (root == nullptr)
        return 0;
    return root->depth;
}

Update Height

static void update_depth(node *root) {
    if (root == nullptr)
        return;
    root->depth = std::max(get_depth(root->lchild), get_depth(root->rchild)) + 1;
}

4. Get the largest/smallest node in the subtree

Principle: According to the binary search tree, the left subtree of a node must be smaller than that node, and the right subtree must be larger than that node.

Get maximum: Traverse directly to get the rightmost node of the node.

    static node* getMaxNode(node* root) {
        if (root == nullptr)
            return nullptr;
        while (root->rchild != nullptr)
            root = root->rchild;
        return root;
    }

Get Minimum: Traverse directly to get the leftmost node of the node.

    static node* getMinNode(node* root) {
        if (root == nullptr)
            return nullptr;
        while (root->lchild != nullptr)
            root = root->lchild;
        return root;
    }

5. Get precursors and successors of nodes

Note: The precursor and successor of a binary search tree generally refers to the preceding and subsequent nodes that it traverses in sequence, that is, the preceding and subsequent nodes from small to large rows.

Check out my previous blog Successors

Subsequent Node Solution: If there is a right subtree, it is the smallest node of the right subtree, if not, the nearest parent node to the right of the node.

static node* getNext(node* root, node* p) { //Get the succeeding nodes of the p-node
    if (root == nullptr || p == nullptr) return nullptr;
    if (p->val >= root->val) {
        return getNext(root->rchild, p);
    } else {
        node* left = getNext(root->lchild, p);
        return left ? left : root;
    }
}

Precursor node solving: If there is a left subtree, it is the largest node of the left subtree, if there is no, it is the nearest parent node to the left of the node.

static node* getPre(node* root, node* p) { //Get the precursor nodes of the p-node
    if (root == nullptr || p == nullptr)return nullptr;
    if (p->val <= root->val) {
        return getPre(root->lchild, p);
    } else {
        node* right = getPre(root->rchild, p);
        return right ? right : root;
    }
}

6.Adjustment of AVL Tree Imbalance

Insertion or deletion of nodes can cause the AVL tree to lose balance, so imbalance adjustment is the basis of insertion and deletion operations.
The unbalance adjustment of AVL trees can be divided into four cases, which we analyze one by one.
Suppose we want to build an AVL tree for the array a[]={4, 5, 6, 3, 2, 8, 7, 0, 1}.
Situation 1: Left-handed
Insert {4, 5, 6} first, and there is an imbalance after inserting element 6:

When we insert the right child into the right subtree causing AVL imbalance, we need to make a single left-handed adjustment. Rotation is around the root node of the minimum imbalance subtree.
It is also possible to have a single left-handed turn when deleting a new node.
The left-hand code is as follows:

static node *rotateLeft(node *root) {
    node *son = root->rchild;
    root->rchild = son->lchild;
    son->lchild = root;
    update_depth(root);
    update_depth(son);
    return son;
}

Scenario 2: Right Hand
Let's continue inserting the element {3,2} when the binary tree is:

There is an imbalance after inserting 3 and 2. At this time the insertion situation is "inserting left child in left subtree causes AVL tree imbalance", we need to make a single right-handed adjustment.
Right-hand code:

static node *rotateRight(node *root) {//Right Hand
    node *son = root->lchild;
    root->lchild = son->rchild;
    son->rchild = root;
    update_depth(root);//Update depth (right-handed only affects these two nodes)
    update_depth(son);
    return son;
}

Scenario 3: Turn left first, then right
The reason you need to rotate twice is that after the first rotation, the AVL tree is still unbalanced and the second rotation is adjusted again.
Let's continue inserting the element {8,7}

In this case, it can be concluded that "inserting a left child in the right subtree causes the AVL tree to become unbalanced". At this point, we need to make a right-hand-to-left adjustment.
The code to adjust is:

static node *rotateLeftRight(node *root) {
    root->lchild = rotateLeft(root->lchild);
    return rotateRight(root);
}

Analysis with examples:

  1. The right child (that is, 8) of the root node (that is, node 6) of the smallest unbalanced subtree is rotated right first.
  2. Next left-handed operation on node 6

Situation 4: Rotate right then left
According to the symmetry principle, when we "insert a right child in the left subtree causes an AVL tree to become unbalanced", we need to make a left-hand-to-right adjustment first. If you don't understand the following picture.
We then insert the node {0,1}

Adjusted code:

static node *rotateRightLeft(node *root) {
    root->rchild = rotateRight(root->rchild);
    return rotateLeft(root);
}

Analysis with examples:

  1. The left child (that is, 0) of the root node (that is, node 2) of the smallest unbalanced subtree is rotated left first.
  2. Do another right-hand operation on Node 2

Summary: Four kinds of imbalance adjustment

7. Insert a new node

//If you need to be compatible with the same elements, you can equal one of the two x<root->val or x>root->vals
static node *Insert(int x, node *root, int& size) { //All updates to deep are post-traversed
    if (root == nullptr) {
        root = new node(x);
        size++;//size++ after node creation
    } else if (x < root->val) {
        root->lchild = Insert(x, root->lchild, size);
        //Since before updating the root node, the update_depth operation must have taken place before the balance is reached
        if (get_depth(root->lchild) - get_depth(root->rchild) == 2)
            root = x < root->lchild->val ? rotateRight(root) : rotateLeftRight(root);
    } else if (x > root->val) {
        root->rchild = Insert(x, root->rchild, size);
        if (get_depth(root->lchild) - get_depth(root->rchild) == -2)
            root = x > root->rchild->val ? rotateLeft(root) : rotateRightLeft(root);
    }
    update_depth(root);
    return root;
}

8. Delete Nodes

Disposal of imbalances:
Deleting a node can also lead to imbalances in the AVL tree, where deleting and inserting nodes are actually a reciprocal operation:

  1. When deleting a node of the right subtree causes an AVL tree to become unbalanced, it is equivalent to inserting a node in the left subtree causing an AVL tree to become unbalanced, that is, case two or case four.
  2. When deleting a node of the left subtree causes the AVL tree to become unbalanced, it is equivalent to inserting a node in the right subtree causing the AVL tree to become unbalanced, that is, case one or case three.

Maintain sorting tree processing:
In addition, AVL trees are also binary sort trees, so the nature of binary sort trees should be maintained when deleting nodes.

  1. If deleting a node is a leaf node, deleting it directly does not change the nature of the search tree.
  2. If a deleted node has only a left or right subtree, the data of the node to be deleted will be directly covered by the next node, and then the next node will be deleted. Because the left and right child pointers of the next layer are copied, no fault will occur.
  3. If a deleted node has both left and right subtrees, find the value of its precursor or successor nodes to cover it(Do not override the pointer, so it is still a sorted binary tree, and then ** Continue to recursively find the corresponding precursor or successor nodes for deletion, ** Because both subtrees have left and right, their precursor or successor can only be leaf nodes, find the direct deletion.

Delete processing code:

I've further optimized the deletion here. If both the left and right subtrees of the deleted node exist, check the height of the left and right subtrees. If the left is higher than the right, select the precursor node to delete and vice versa, follow up.

static node *remove(int x, node *root, int& size) {
    if (root == nullptr)
        return nullptr;

    if (x == root->val) {
        /*Neither left nor right subtree is empty - replace with a middle-order precursor or successor*/
        if (root->lchild != nullptr && root->rchild != nullptr) {
            /*Choose which side to replace by deleting based on the depth of the left and right subtrees*/
            if (get_depth(root->lchild) > get_depth(root->rchild)) {
                node* t = getMaxNode(root->lchild);
                root->val = t->val;
                root->lchild = remove(t->val, root->lchild, size);
            } else {
                node* t = getMinNode(root->rchild);
                root->val = t->val;
                root->rchild = remove(t->val, root->rchild, size);
            }
        }
        /*At least one of the left and right subtrees is empty, just go down one step*/
        else {
            node* tmp = root->lchild == nullptr ? root->rchild : nullptr;
            if (tmp != nullptr) {
                *root = *tmp;
                delete tmp;
            }
            else {
                delete root;
                root = nullptr;
            }
            //Delete size--
            size--;
        }
    } else if (x < root->val) {
        root->lchild = remove(x, root->lchild, size);
        if (get_depth(root->lchild) - get_depth(root->rchild) == -2)
            root = get_depth(root->rchild->lchild) > get_depth(root->rchild->rchild) ? rotateRightLeft(root) : rotateLeft(root);
    } else {
        root->rchild = remove(x, root->rchild, size);
        if (get_depth(root->lchild) - get_depth(root->rchild) == 2)
            root = get_depth(root->lchild->rchild) > get_depth(root->lchild->lchild) ? rotateLeftRight(root) : rotateRight(root);
    }
    return root;
}

9.Find Elements

Binary tree is a recursive definition, so many operations of a binary tree can be implemented simply by recursion, such as traversing a binary tree, finding a specified element, destroying a binary tree, and so on.

Iteration is used here.

bool find(int x) {
    //Find direct iteration
    node *f = head;
    while (f != nullptr) {
        if (x == f->val)
            return true;
        else if (x < f->val)
            f = f->lchild;
        else
            f = f->rchild;
    }
    return false;
}

10.Traversing Binary Trees

  • I've only provided a middle-order traversed print to verify the binary search tree.
static void inorder(node *root) {
    if (root != nullptr) {
        inorder(root->lchild);
        printf("%d ", root->val);
        inorder(root->rchild);
    }
}

11.Destroy of AVL Tree

Directly use sequencing to process left and right subtrees before processing root nodes.

static void destroy(node *root) {
    if (root == nullptr)
        return;
    destroy(root->lchild);
    destroy(root->rchild);
    delete root;
    root = nullptr;
}

12.Iterator Design

  • As for iterators inside C++, they are actually convenient for traversing elements in containers, and iterators need to simulate the behavior of pointers, so in C++, iterators are actually classes that specially wrap pointers and overload some of their operators.

Internal classes act as iterators

Since the iteration order of sequential containers needs to be satisfied, the ++ and - Operations correspond to the successor and the precursor.

/*iterator part*/
    class iterator { //Encapsulation Iterator
        node* head;
        node* root;
    public:
        iterator(node* head, node* root): head(head), root(root) {}
        iterator& operator++() { //Add root directly to the current successor node
            root = getNext(head, root);
            return *this;
        }
        bool operator==(const iterator& x) {
            return this->root == x.root;
        }
        bool operator!=(const iterator& x) {
            return this->root != x.root;
        }
        iterator operator++(int) {
            iterator t = *this;
            root = getNext(head, root);
            return t;
        }
        iterator& operator--() { //Assigning root directly to a precursor node
            root = getPre(head, root);
            return *this;
        }
        iterator operator--(int) {
            iterator t = *this;
            root = getPre(head, root);
            return t;
        }
        node& operator*() { //Unquoted overload
            return *root;
        }
    };

External classes provide external begin() and end() interfaces to get the start and end of an iterator

    iterator begin() {
        node* min = getMinNode(head);
        return iterator(head, min);
    }
    iterator end() { //End denotes end tag
        return iterator(head, nullptr);
    }

Complete Code

My GitHub

AVLTree.h

//
// Created by Alone on 2021/10/12.
//
#include <algorithm>
#include <cstdio>
#include <cassert>

#ifndef MY_TINY_STL_AVLTREE_H
#define MY_TINY_STL_AVLTREE_H
namespace L_B__ {
    struct node {
        int val;
        int depth;
        node *lchild;
        node *rchild;

        node() : val(0), lchild(nullptr), rchild(nullptr) {}

        node(int x) : val(x), lchild(nullptr), rchild(nullptr) {}
    };

    class AVLTree {
        /*date part*/
        node *head;
        int length;
    public:
        /*construct and destruct part*/
        AVLTree() : head(nullptr), length(0) {}

        AVLTree(int x) : head(new node(x)), length(1) {}

        ~AVLTree() {
            destroy(head);
        }

    public:
        /*iterator part*/
        class iterator {//Encapsulation Iterator: Internal Class - only static functions of external classes can be called
            node *head;
            node *root;
        public:
            iterator(node *head, node *root) : head(head), root(root) {}

            iterator &operator++();

            bool operator==(const iterator &x);

            bool operator!=(const iterator &x);

            iterator operator++(int);

            iterator &operator--();

            iterator operator--(int);

            int operator*();
        };

    private:
        /*static member function*/
        /*Rotate Part*/
        static node *rotateRight(node *root);

        static node *rotateLeft(node *root);

        static node *rotateLeftRight(node *root);

        static node *rotateRightLeft(node *root);

        /*Destruct*/
        static void destroy(node *root);

        /*Getter*/
        static node *getNext(node *root, node *p);

        static node *getPre(node *root, node *p);

        static node *getMinNode(node *root);

        static node *getMaxNode(node *root);

        static int get_depth(node *root);

        static void update_depth(node *root);

        /*Insert&Remove*/
        static node *Insert(int x, node *root, int &size);

        static node *remove(int x, node *root, int &size);

        /*print_order*/
        static void inorder(node *root);

    public:
        /*public interface*/
        /*clear&empty*/
        void clear();

        bool isEmpty();

        /*find*/
        bool find(int x);

        /*insert&remove*/
        void insert(int x);

        void remove(int x);

        /*size*/
        int size();

        /*begin&end*/
        iterator begin();

        iterator end();

        /*print*/
        void inorder_print();
    };
}


#endif //MY_TINY_STL_AVLTREE_H

AVLTree.cpp

//
// Created by Alone on 2021/10/12.
//

#include "AVLTree.h"

/*Rotate*/
L_B__::node *L_B__::AVLTree::rotateRight(L_B__::node *root) {//Right Hand
    node *son = root->lchild;
    root->lchild = son->rchild;
    son->rchild = root;
    update_depth(root);//Update depth (right-handed only affects these two nodes)
    update_depth(son);
    return son;
}

L_B__::node *L_B__::AVLTree::rotateLeft(L_B__::node *root) {
    node *son = root->rchild;
    root->rchild = son->lchild;
    son->lchild = root;
    update_depth(root);
    update_depth(son);
    return son;
}

L_B__::node *L_B__::AVLTree::rotateLeftRight(L_B__::node *root) {
    root->lchild = rotateLeft(root->lchild);
    return rotateRight(root);
}

L_B__::node *L_B__::AVLTree::rotateRightLeft(L_B__::node *root) {
    root->rchild = rotateRight(root->rchild);
    return rotateLeft(root);
}


/*Destruct*/
void L_B__::AVLTree::destroy(L_B__::node *root) {
    if (root == nullptr)
        return;
    destroy(root->lchild);
    destroy(root->rchild);
    delete root;
    root = nullptr;
}


/*Getter*/
L_B__::node *L_B__::AVLTree::getNext(L_B__::node *root, L_B__::node *p) {
    if (root == nullptr || p == nullptr) return nullptr;
    if (p->val >= root->val) {
        return getNext(root->rchild, p);
    } else {
        node *left = getNext(root->lchild, p);
        return left ? left : root;
    }
}

L_B__::node *L_B__::AVLTree::getPre(L_B__::node *root, L_B__::node *p) {
    if (root == nullptr || p == nullptr)return nullptr;
    if (p->val <= root->val) {
        return getPre(root->lchild, p);
    } else {
        node *right = getPre(root->rchild, p);
        return right ? right : root;
    }
}

L_B__::node *L_B__::AVLTree::getMinNode(L_B__::node *root) {
    if (root == nullptr)
        return nullptr;
    while (root->lchild != nullptr)
        root = root->lchild;
    return root;
}

L_B__::node *L_B__::AVLTree::getMaxNode(L_B__::node *root) {
    if (root == nullptr)
        return nullptr;
    while (root->rchild != nullptr)
        root = root->rchild;
    return root;
}

int L_B__::AVLTree::get_depth(L_B__::node *root) {
    if (root == nullptr)
        return 0;
    return root->depth;
}

void L_B__::AVLTree::update_depth(L_B__::node *root) {
    if (root == nullptr)
        return;
    root->depth = std::max(get_depth(root->lchild), get_depth(root->rchild)) + 1;
}


/*Insert&remove*/
L_B__::node *L_B__::AVLTree::Insert(int x, L_B__::node *root, int &size) {
    if (root == nullptr) {
        root = new node(x);
        size++;//size++ after node creation
    } else if (x < root->val) {
        root->lchild = Insert(x, root->lchild, size);
        //Since before updating the root node, the update_depth operation must have taken place before the balance is reached
        if (get_depth(root->lchild) - get_depth(root->rchild) == 2)
            root = x < root->lchild->val ? rotateRight(root) : rotateLeftRight(root);
    } else if (x > root->val) {
        root->rchild = Insert(x, root->rchild, size);
        if (get_depth(root->lchild) - get_depth(root->rchild) == -2)
            root = x > root->rchild->val ? rotateLeft(root) : rotateRightLeft(root);
    }
    update_depth(root);
    return root;
}

L_B__::node *L_B__::AVLTree::remove(int x, L_B__::node *root, int &size) {
    if (root == nullptr)
        return nullptr;

    if (x == root->val) {
        /*Neither left nor right subtree is empty - replace with a middle-order precursor or successor*/
        if (root->lchild != nullptr && root->rchild != nullptr) {
            /*Choose which side to replace by deleting based on the depth of the left and right subtrees*/
            if (get_depth(root->lchild) > get_depth(root->rchild)) {
                node *t = getMaxNode(root->lchild);
                root->val = t->val;
                root->lchild = remove(t->val, root->lchild, size);
            } else {
                node *t = getMinNode(root->rchild);
                root->val = t->val;
                root->rchild = remove(t->val, root->rchild, size);
            }
        }
            /*At least one of the left and right subtrees is empty, just go down one step*/
        else {
            node *tmp = root->lchild == nullptr ? root->rchild : nullptr;
            if (tmp != nullptr) {
                *root = *tmp;
                delete tmp;
            } else {
                delete root;
                root = nullptr;
            }
            //Delete size--
            size--;
        }
    } else if (x < root->val) {
        root->lchild = remove(x, root->lchild, size);
        if (get_depth(root->lchild) - get_depth(root->rchild) == -2)
            root = get_depth(root->rchild->lchild) > get_depth(root->rchild->rchild) ? rotateRightLeft(root)
                                                                                     : rotateLeft(root);
    } else {
        root->rchild = remove(x, root->rchild, size);
        if (get_depth(root->lchild) - get_depth(root->rchild) == 2)
            root = get_depth(root->lchild->rchild) > get_depth(root->lchild->lchild) ? rotateLeftRight(root)
                                                                                     : rotateRight(root);
    }
    return root;
}


/*print part*/
void L_B__::AVLTree::inorder(L_B__::node *root) {
    if (root != nullptr) {
        inorder(root->lchild);
        printf("%d ", root->val);
        inorder(root->rchild);
    }
}


/*clear&isEmpty*/
void L_B__::AVLTree::clear() {
    destroy(head);
}

bool L_B__::AVLTree::isEmpty() {
    return head == nullptr;
}


/*find*/
bool L_B__::AVLTree::find(int x) {
    //Find direct iteration
    node *f = head;
    while (f != nullptr) {
        if (x == f->val)
            return true;
        else if (x < f->val)
            f = f->lchild;
        else
            f = f->rchild;
    }
    return false;
}


/*insert&remove*/
void L_B__::AVLTree::insert(int x) {
    head = Insert(x, head, length);
}

void L_B__::AVLTree::remove(int x) {
    assert(length != 0);
    head = remove(x, head, length);
}

int L_B__::AVLTree::size() {
    return length;
}


/*begin&end*/
L_B__::AVLTree::iterator L_B__::AVLTree::begin() {
    node *min = getMinNode(head);
    return iterator(head, min);
}

L_B__::AVLTree::iterator L_B__::AVLTree::end() {
    return iterator(head, nullptr);
}

/*print*/
void L_B__::AVLTree::inorder_print() {
    printf("Inorder:# ");
    inorder(head);
}


/*iterator implement*/
bool L_B__::AVLTree::iterator::operator==(const L_B__::AVLTree::iterator &x) {
    return this->root == x.root;
}

bool L_B__::AVLTree::iterator::operator!=(const L_B__::AVLTree::iterator &x) {
    return this->root != x.root;
}

L_B__::AVLTree::iterator L_B__::AVLTree::iterator::operator++(int) {
    iterator t = *this;
    root = getNext(head, root);
    return t;
}

L_B__::AVLTree::iterator &L_B__::AVLTree::iterator::operator--() {
    root = getPre(head, root);
    return *this;
}

L_B__::AVLTree::iterator L_B__::AVLTree::iterator::operator--(int) {
    iterator t = *this;
    root = getPre(head, root);
    return t;
}

int L_B__::AVLTree::iterator::operator*() {
    return root->val;
}

L_B__::AVLTree::iterator &L_B__::AVLTree::iterator::operator++() {
    root = getNext(head, root);
    return *this;
}

test

  • Note: The following data can achieve good insertion performance because there are a lot of identical values, and the AVLTree I wrote does not store the same values, so it saves a lot of adjustment time during insertion. In fact, as long as there is enough data actually inserted, the gap between red and black trees will be wider. I have tried the test of inserting 1 billion non-repeating data into AVL and RB before.The VL runs for a few minutes and the RB resolves in one minute. However, the AVL is still hoisting the RB in terms of finding and deleting (after all, strictly balanced trees)

Contrast with set in STL:

Test Code

int main() {
    using namespace std;
    AVLTree x;
    set<int>Q;
    printf("Insert Test\n");
    auto start = clock();
    for (int i = 0; i < 100000000; ++i) {
            x.insert(i%10000000);
    }
    std::cout<<"AVLTree"<<clock()-start<<"ms"<<std::endl;
    start = clock();
    for (int i = 0; i < 100000000; ++i) {
        Q.insert(i%10000000);
    }
    std::cout<<"RBTree"<<clock()-start<<"ms"<<std::endl;
    printf("Iterative testing\n");
    start = clock();
    for(auto it = x.begin();it!=x.end();++it){
        continue;
    }
    std::cout<<"AVLTree"<<clock()-start<<"ms"<<std::endl;
    start = clock();
    for(auto it = Q.begin();it!=Q.end();++it){
        continue;
    }
    std::cout<<"RBTree"<<clock()-start<<"ms"<<std::endl;
    printf("Find Tests\n");
    start = clock();
    for (int i = 0; i < 100000000; ++i) {
        x.find(i);
    }
    std::cout<<"AVLTree"<<clock()-start<<"ms"<<std::endl;
    start = clock();
    for(int i = 0;i<100000000;++i){
        Q.count(i);
    }
    std::cout<<"RBTree"<<clock()-start<<"ms"<<std::endl;
    printf("Delete Tests\n");
    start = clock();
    for(int i=0;i<10000000;i++){
        x.remove(i);
    }
    std::cout<<"AVLTree"<<clock()-start<<"ms"<<"length"<<x.size()<<std::endl;
    start = clock();
    for(int i=0;i<10000000;i++){
        Q.erase(i);
    }
    std::cout<<"RBTree"<<clock()-start<<"ms"<<"length"<<Q.size()<<std::endl;
    return 0;
}

Test Summary

By continuously comparing red and black trees (RB) with AVL, the following conclusions are drawn:

  1. The insertion operation of red and black trees is much faster than AVL, the larger the amount of data, the more obvious the advantage.
  2. Finding and deleting red and black trees is much slower than AVL, and the larger the amount of data, the more obvious it is.

Overall, if you need to manage a large amount of data and need frequent insertions, then red and black trees are more suitable for you, if you only need to insert once, to find and manage the data, then AVL is more suitable for you!

Problem solving test


OJ Platform

Add a get_head method to the above design.

#include <bits/stdc++.h>

struct node {
    int val;
    int depth;
    node *lchild;
    node *rchild;

    node() : val(0), lchild(nullptr), rchild(nullptr) {}

    node(int x) : val(x), lchild(nullptr), rchild(nullptr) {}
};

class AVLTree {
    /*date part*/
    node *head;
    int size;
public:
    /*construct and destruct part*/
    AVLTree() : head(nullptr),size(0) {}

    AVLTree(int x) : head(new node(x)),size(1) {}

    ~AVLTree() {
        destroy(head);
    }

private:
    /*static function part*/
    static node *rotateRight(node *root) {//Right Hand
        node *son = root->lchild;
        root->lchild = son->rchild;
        son->rchild = root;
        update_depth(root);//Update depth (right-handed only affects these two nodes)
        update_depth(son);
        return son;
    }

    static node *rotateLeft(node *root) {
        node *son = root->rchild;
        root->rchild = son->lchild;
        son->lchild = root;
        update_depth(root);
        update_depth(son);
        return son;
    }

    static node *rotateLeftRight(node *root) {
        root->lchild = rotateLeft(root->lchild);
        return rotateRight(root);
    }

    static node *rotateRightLeft(node *root) {
        root->rchild = rotateRight(root->rchild);
        return rotateLeft(root);
    }

    static void destroy(node *root) {
        if (root == nullptr)
            return;
        destroy(root->lchild);
        destroy(root->rchild);
        delete root;
        root = nullptr;
    }
    static node* getMinNode(node* root){
        if(root== nullptr)
            return nullptr;
        while(root->lchild!= nullptr)
            root = root->lchild;
        return root;
    }
    static node* getMaxNode(node* root){
        if(root== nullptr)
            return nullptr;
        while(root->rchild!= nullptr)
            root = root->rchild;
        return root;
    }
    static int get_depth(node *root) {//Get Depth
        if (root == nullptr)
            return 0;
        return root->depth;
    }

    static void update_depth(node *root) {
        if (root == nullptr)
            return;
        root->depth = std::max(get_depth(root->lchild), get_depth(root->rchild)) + 1;
    }

    static node *Insert(int x, node *root) {//All updates to deep are post-traversed
        if (root == nullptr) {
            root = new node(x);
        } else if (x <= root->val) {
            root->lchild = Insert(x, root->lchild);
            //Since before updating the root node, the update_depth operation must have taken place before the balance is reached
            if (get_depth(root->lchild) - get_depth(root->rchild) == 2)
                root = x <= root->lchild->val ? rotateRight(root) : rotateLeftRight(root);
        } else if (x > root->val) {
            root->rchild = Insert(x, root->rchild);
            if (get_depth(root->lchild) - get_depth(root->rchild) == -2)
                root = x >= root->rchild->val ? rotateLeft(root) : rotateRightLeft(root);
        }
        update_depth(root);
        return root;
    }

    static node *remove(int x, node *root) {
        if (root == nullptr)
            return nullptr;

        if (x == root->val) {
            /*Neither left nor right subtree is empty - replace with a middle-order precursor or successor*/
            if (root->lchild != nullptr && root->rchild != nullptr) {
                /*Choose which side to replace by deleting based on the depth of the left and right subtrees*/
                if(get_depth(root->lchild)>get_depth(root->rchild)){
                    node* t = getMaxNode(root->lchild);
                    root->val = t->val;
                    root->lchild = remove(t->val,root->lchild);
                }else{
                    node* t = getMinNode(root->rchild);
                    root->val = t->val;
                    root->rchild = remove(t->val,root->rchild);
                }
            }
            /*At least one of the left and right subtrees is empty, just go down one step*/
            else{
                node* tmp = root->lchild== nullptr?root->rchild: nullptr;
                if(tmp!= nullptr){
                    *root = *tmp;
                    delete tmp;
                }
                else{
                    delete root;
                    root = nullptr;
                }
            }
        } else if (x < root->val) {
            root->lchild = remove(x, root->lchild);
            if(get_depth(root->lchild)-get_depth(root->rchild)==-2)
                root = get_depth(root->rchild->lchild)>get_depth(root->rchild->rchild)?rotateRightLeft(root): rotateLeft(root);
        } else {
            root->rchild = remove(x, root->rchild);
            if(get_depth(root->lchild)-get_depth(root->rchild)==2)
                root = get_depth(root->lchild->rchild)>get_depth(root->lchild->lchild)?rotateLeftRight(root): rotateRight(root);
        }
        return root;
    }

    static void inorder(node *root) {
        if (root != nullptr) {
            inorder(root->lchild);
            printf("%d ", root->val);
            inorder(root->rchild);
        }
    }

public:
    /*public function part*/
    void insert(int x) {
        //Recursive insertion for subsequent processing
        head = Insert(x, head);
        size++;
    }
    void remove(int x){
        assert(size!=0);
        head = remove(x,head);
        size--;
    }
    void clear() {
        destroy(head);
    }

    bool isEmpty() {
        return head == nullptr;
    }

    bool find(int x) {
        //Find direct iteration
        node *f = head;
        while (f != nullptr) {
            if (x == f->val)
                return true;
            else if (x < f->val)
                f = f->lchild;
            else
                f = f->rchild;
        }
        return false;
    }

    void inorder_print() {
        printf("Inorder:# ");
        inorder(head);
    }
    node* get_head(){
        return head;
    }
};

int main() {
    AVLTree x;
    int n;std::cin>>n;
    while(n--){
        int t;
        std::cin>>t;
        x.insert(t);
    }
    printf("%d",x.get_head()->val);
    return 0;
}

Tags: C++ Algorithm data structure

Posted on Tue, 12 Oct 2021 12:40:53 -0400 by Redapple