Article directory
- Why have AVL trees?
- What is an AVL tree
- Implementation of AVL tree
-
- Insertion of elements
- Balance factor update
- Rotation of AVL tree
- AVL tree inspection
- Complete implementation
This article summarizes all the contents in the AVL
tree, with detailed illustrations of the process.
Why there is an AVL tree
We gave a brief introduction to map/multimap/set/multiset
earlier. In its documentation, we found that these containers have one thing in common: their bottom layers are based on binary search trees. Implemented, but the binary search tree has its own flaws. If the elements inserted into the tree are ordered or close to ordered, the binary search tree will degenerate into a single tree, and the time complexity will degenerate into O (N)
, so the underlying structure of associative containers such as map and set
balances binary trees, that is, using balanced trees.
What is an AVL tree
When a binary search tree is shaped like the following scenario, the search efficiency will be quite low.
Even when it is close to a single-branch tree, the search efficiency will be equivalent to the search efficiency in the sequence table, so the Russian mathematicians G.M. Adelson-Velskii
and E.M. Landis
invented I came up with a solution: when inserting a new node into the binary search tree, if it can be ensured that the absolute value of the difference between the heights of the left and right subtrees of each node does not exceed 1
(it needs to be updated in the tree By adjusting the nodes), you can reduce the height of the tree, thereby reducing the average search length. This is the origin of the AVL
tree.
An AVL
tree is either an empty tree or a binary search tree with the following properties:
- Its left and right subtrees are both
AVL
trees - The absolute value of the difference between the heights of the left and right subtrees does not exceed
1
The height difference between the left and right subtrees is also called the balance factor. The time complexity of searching in such a tree is O(logN)
Implementation of AVL tree
Insertion of elements
First define the node. Since the AVL
tree has a need to search for information upwards, when defining the node information, there must be a pointer to the parent of the node.
// Define the nodes of the AVL tree template<class K, class V> structAVLTreeNode {<!-- --> AVLTreeNode(const pair<K, V> & amp; kv) :_left(nullptr) , _right(nullptr) , _parent(nullptr) , _kv(val) , bf(0) {<!-- -->} // Left subtree pointer, right subtree pointer, parent node pointer, node value, balance factor AVLTreeNode<K, V>* _left; AVLTreeNode<K, V>* _right; AVLTreeNode<K, V>* _parent; pair<K, V> _kv; int bf; };
The following defines the insertion of AVL
tree. The insertion of AVL
tree is mainly inserted into a binary search tree first, and then the binary search tree is balanced, so it must be inserted first to binary search tree
bool Insert(const pair<K, V> kv) {<!-- --> Node* newnode = new Node(kv); if (_root == nullptr) {<!-- --> _root = newnode; return true; } Node* cur = _root; Node* parent = nullptr; while (cur) {<!-- --> if (newnode->_kv.first > cur->_kv.first) {<!-- --> parent = cur; cur = cur->_left; } else if (newnode->_kv.first < cur->_kv.first) {<!-- --> parent = cur; cur = cur->_right; } else {<!-- --> return false; } } if (newnode->_kv.first > parent->_kv.first) {<!-- --> parent->_right = newnode; cur->_parent = parent; } else {<!-- --> parent->_left = newnode; cur->_parent = parent; } }
The above is the basic part of the binary search tree. Next is the AVL
tree balancing problem.
Update of balance factors
After a new node is inserted, the balance of the AVL
tree may be destroyed. This is the significance of introducing the balance factor. Therefore, the first step after the insertion is to update the balance factor to see if it has been damaged. to damage, the adjustment of the balance factor has the following situations:
- If inserted to the left side of the node, the balance factor is -1
- If inserted to the right of the node, the balance factor + 1 is enough
There will be new problems after the node is inserted. See the following scenario.
Based on this principle, code can be written to update the balance factor
// Insertion of nodes // The logic of node insertion is basically the same as that of the search tree, except that it needs to be rotated if it does not meet the requirements. bool Insert(const pair<K, V> kv) {<!-- --> Node* newnode = new Node(kv); if (_root == nullptr) {<!-- --> _root = newnode; return true; } Node* cur = _root; Node* parent = nullptr; while (cur) {<!-- --> if (newnode->_kv.first > cur->_kv.first) {<!-- --> parent = cur; cur = cur->_left; } else if (newnode->_kv.first < cur->_kv.first) {<!-- --> parent = cur; cur = cur->_right; } else {<!-- --> return false; } } if (newnode->_kv.first > parent->_kv.first) {<!-- --> parent->_right = newnode; cur->_parent = parent; } else {<!-- --> parent->_left = newnode; cur->_parent = parent; } //The above is the basic part of the binary search tree, and the balance factor is updated below. while (parent) {<!-- --> // If cur is the left node of parent, the balance factor is -1, if it is the right node, the balance factor + 1 if (cur == parent->_left) {<!-- --> parent->_bf--; } else {<!-- --> parent->_bf + + ; } // Determine the absolute value of the parent node's balance factor: // 1. If it is 0, it proves that no upward update is needed // 2. If it is 1, it needs to be updated upwards // 3. If it is 2, rotation is required if (parent->_bf == 0) {<!-- --> break; } else if (parent->_bf == 1 || parent->_bf == -1) {<!-- --> cur = parent; parent = parent->_parent; } else if (parent->_bf == 2 || parent->_bf == -2) {<!-- --> // Start rotating } else {<!-- --> // If the absolute value is greater than 2, it means that the balance factor is wrong and the end is forced. assert(false); } } }
AVL tree rotation
When the above situation has occurred, how should the AVL
tree be balanced? This introduces the concept of rotation
In the AVL
tree, when the balance of the AVL
tree is destroyed, there are a total of four rotations that will be caused
1. The new node is inserted to the left side of the higher left subtree, causing a right single rotation
Therefore, the following code implementation implements right-handed rotation:
// Right single rotation void RotateR(Node* parent) {<!-- --> Node* subL = parent->_left; Node* subLR = subL->_right; parent->_left = subLR; if(subLR) subLR->_parent = parent; Node* parentParent = parent->_parent; subL->_right = parent; parent->_parent = subL; if (_root == parent) {<!-- --> _root = subL; subL->_parent = nullptr; } else {<!-- --> if (parentParent->_left == parent) {<!-- --> parentParent->_left = subL; } else {<!-- --> parentParent->_right = subL; } subL->_parent = parentParent; } subL->_bf = parent->_bf = 0; }
2. The new node is inserted on the right side of the higher right subtree, causing a single left rotation
// Single left rotation void RotateL(Node* parent) {<!-- --> Node* subR = parent->_right; Node* subRL = subR->_left; Node* parentParent = parent->_parent; //Update node parent->_right = subRL; parent->_parent = subR; subR->_left = parent; subR->_parent = parentParent; if(subRL) subRL->_parent = parent; // update parentParent if (_root == parent) {<!-- --> _root = subR; } else {<!-- --> if (parentParent->_left == parent) {<!-- --> parentParent->_left = subR; } else {<!-- --> parentParent->_right = subR; } } parent->_bf = subR->_bf = 0; }
3. The new node is inserted to the left side of the higher right subtree, causing a right single rotation and then a left single rotation
For this situation, there are a total of the following two situations:
There is also an idea for code implementation. First rotate left and then rotate right, and finally modify the value of the balance factor. To modify the value of the balance factor, you can use bf
of subRL
Values can be used to judge different situations, and different values can be modified according to different situations.
// First rotate right and then rotate left void RotateRL(Node* parent) {<!-- --> Node* subR = parent->_right; Node* subRL = subR->_left; int bf = subRL->_bf; RotateR(subR); RotateL(parent); if (bf == 0) {<!-- --> // Itself is the newly added node parent->_bf = subR->_bf = 0; } else if (bf == -1) {<!-- --> // Insertion in left subtree parent->_bf = 0; subR->_bf = 1; subRL->_bf = 0; } else if (bf == 1) {<!-- --> // Insertion in right subtree subRL->_bf = 0; subR->_bf = 0; parent->_bf = -1; } else {<!-- --> assert(false); } }
4. The new node is inserted to the right of the higher left subtree, causing a single left rotation and then a single right rotation
The analysis method is similar to the previous one
// First perform left single rotation, then perform right single rotation void RotateLR(Node* parent) {<!-- --> Node* subL = parent->_left; Node* subLR = subL->_right; int bf = subLR->_bf; RotateL(subL); RotateR(parent); // Replace the balance factor if (bf == 0) {<!-- --> subL->_bf = subLR->_bf = 0; } else if (bf == 1) {<!-- --> //Insert in the right subtree subL->_bf = -1; subLR->_bf = 0; parent->_bf = 0; } else if (bf == -1) {<!-- --> //Insert in left subtree parent->_bf = 1; subL->_bf = 0; subLR->_bf = 0; } else {<!-- --> assert(false); } }
AVL tree inspection
Based on the above implementation, the AVL
tree can be basically realized. How to verify whether the AVL
tree is correct? In fact, the verification is also very simple. You only need to check whether the balance factor of each node is equal to the value of the corresponding right subtree minus the left subtree.
// Used to check the height of the tree int TreeHeight(Node* root) {<!-- --> if (root == nullptr) return 0; int leftheight = TreeHeight(root->_left); int rightheight = TreeHeight(root->_right); return max(leftheight, rightheight) + 1; } // Keep the tree encapsulated and check the AVL tree boolIsBalance() {<!-- --> return _IsBalance(root); } // Check the AVL tree bool _IsBalance(Node* root) {<!-- --> if (root == nullptr) return true; int leftheight = TreeHeight(root->_left); int rightheight = TreeHeight(root->_right); if (rightheight - leftheight != root->_bf) return false; return abs(rightheight-leftheight)<2 & amp; & amp; _IsBalance(root->_left) & amp; & amp; _IsBalance(root->_right); }
This completes the test of the AVL
tree
Complete implementation
#include <iostream> #include <assert.h> using namespace std; // Define the nodes of the AVL tree template<class K, class V> structAVLTreeNode {<!-- --> AVLTreeNode(const pair<K, V> & amp; kv) :_left(nullptr) , _right(nullptr) , _parent(nullptr) , _kv(kv) , _bf(0) {<!-- -->} // Left subtree pointer, right subtree pointer, parent node pointer, node value, balance factor AVLTreeNode<K, V>* _left; AVLTreeNode<K, V>* _right; AVLTreeNode<K, V>* _parent; pair<K, V> _kv; int _bf; }; //Define AVL tree template<class K, class V> classAVLTree {<!-- --> typedef AVLTreeNode<K, V> Node; public: AVLTree() :_root(nullptr) {<!-- -->} // Insertion of nodes // The logic of node insertion is basically the same as that of the search tree, except that it needs to be rotated if it does not meet the requirements. bool Insert(const pair<K, V> kv) {<!-- --> if (_root == nullptr) {<!-- --> _root = new Node(kv); return true; } Node* cur = _root; Node* parent = nullptr; while (cur) {<!-- --> if (kv.first > cur->_kv.first) {<!-- --> parent = cur; cur = cur->_right; } else if (kv.first < cur->_kv.first) {<!-- --> parent = cur; cur = cur->_left; } else {<!-- --> return false; } } cur = new Node(kv); if (kv.first > parent->_kv.first) {<!-- --> parent->_right = cur; cur->_parent = parent; } else {<!-- --> parent->_left = cur; cur->_parent = parent; } //The above is the basic part of the binary search tree, and the balance factor is updated below. while (parent) {<!-- --> // If cur is the left node of parent, the balance factor is -1, if it is the right node, the balance factor + 1 if (cur == parent->_left) {<!-- --> parent->_bf--; } else {<!-- --> parent->_bf + + ; } // Determine the absolute value of the parent node's balance factor: // 1. If it is 0, it proves that no upward update is needed // 2. If it is 1, it needs to be updated upwards // 3. If it is 2, rotation is required if (parent->_bf == 0) {<!-- --> break; } else if (parent->_bf == 1 || parent->_bf == -1) {<!-- --> cur = parent; parent = parent->_parent; } else if (parent->_bf == 2 || parent->_bf == -2) {<!-- --> // Start rotating if (parent->_bf == 2 & amp; & amp; cur->_bf == 1) {<!-- --> RotateL(parent); } else if (parent->_bf == -2 & amp; & amp; cur->_bf == -1) {<!-- --> RotateR(parent); } else if (parent->_bf == 2 & amp; & amp; cur->_bf == -1) {<!-- --> RotateRL(parent); } else if (parent->_bf == -2 & amp; & amp; cur->_bf == 1) {<!-- --> RotateLR(parent); } break; } else {<!-- --> // If the absolute value is greater than 2, it means that the balance factor is wrong and the end is forced. assert(false); } } return true; } // right single rotation void RotateR(Node* parent) {<!-- --> Node* subL = parent->_left; Node* subLR = subL->_right; parent->_left = subLR; if(subLR) subLR->_parent = parent; Node* parentParent = parent->_parent; subL->_right = parent; parent->_parent = subL; if (_root == parent) {<!-- --> _root = subL; subL->_parent = nullptr; } else {<!-- --> if (parentParent->_left == parent) {<!-- --> parentParent->_left = subL; } else {<!-- --> parentParent->_right = subL; } subL->_parent = parentParent; } subL->_bf = parent->_bf = 0; } // left single rotation void RotateL(Node* parent) {<!-- --> Node* subR = parent->_right; Node* subRL = subR->_left; Node* parentParent = parent->_parent; //Update node parent->_right = subRL; parent->_parent = subR; subR->_left = parent; subR->_parent = parentParent; if(subRL) subRL->_parent = parent; // update parentParent if (_root == parent) {<!-- --> _root = subR; } else {<!-- --> if (parentParent->_left == parent) {<!-- --> parentParent->_left = subR; } else {<!-- --> parentParent->_right = subR; } } parent->_bf = subR->_bf = 0; } // First rotate right and then rotate left void RotateRL(Node* parent) {<!-- --> Node* subR = parent->_right; Node* subRL = subR->_left; int bf = subRL->_bf; RotateR(subR); RotateL(parent); if (bf == 0) {<!-- --> // Itself is the newly added node parent->_bf = subR->_bf = 0; } else if (bf == -1) {<!-- --> // Insertion in left subtree parent->_bf = 0; subR->_bf = 1; subRL->_bf = 0; } else if (bf == 1) {<!-- --> // Insertion in right subtree subRL->_bf = 0; subR->_bf = 0; parent->_bf = -1; } else {<!-- --> assert(false); } } // First perform left single rotation, then perform right single rotation void RotateLR(Node* parent) {<!-- --> Node* subL = parent->_left; Node* subLR = subL->_right; int bf = subLR->_bf; RotateL(subL); RotateR(parent); // Replace the balance factor if (bf == 0) {<!-- --> subL->_bf = subLR->_bf = 0; } else if (bf == 1) {<!-- --> //Insert in the right subtree subL->_bf = -1; subLR->_bf = 0; parent->_bf = 0; } else if (bf == -1) {<!-- --> //Insert in left subtree parent->_bf = 1; subL->_bf = 0; subLR->_bf = 0; } else {<!-- --> assert(false); } } // Used to check the height of the tree int TreeHeight(Node* root) {<!-- --> if (root == nullptr) return 0; int leftheight = TreeHeight(root->_left); int rightheight = TreeHeight(root->_right); return max(leftheight, rightheight) + 1; } // Keep the tree encapsulated and check the AVL tree boolIsBalance() {<!-- --> return _IsBalance(root); } // Check the AVL tree bool _IsBalance(Node* root) {<!-- --> if (root == nullptr) return true; int leftheight = TreeHeight(root->_left); int rightheight = TreeHeight(root->_right); if (rightheight - leftheight != root->_bf) return false; return abs(rightheight-leftheight)<2 & amp; & amp; _IsBalance(root->_left) & amp; & amp; _IsBalance(root->_right); } private: Node* _root; };